From 77585cf8f1cad28a29d787c07d072a84d201ee2f Mon Sep 17 00:00:00 2001 From: HRS Date: Tue, 4 Aug 2026 08:02:48 +0200 Subject: [PATCH 01/15] Add @stylistic/eslint-plugin and enforce formatting rules - Add @stylistic/eslint-plugin dependency and stylistic.configs.recommended - Configure indent (4 spaces), semi (always), comma-dangle (always-multiline) - Configure member-delimiter-style (semi for multiline, comma for singleline) - Configure operator-linebreak (before, with = kept after for multiline type declarations) - Configure indent-binary-ops (4 spaces, matching general indent) - Configure arrow-parens (as-needed, no exception for block bodies) - Require blank line between type/interface/class/enum declarations and a following export statement - Disable brace-style (mixed Allman/1tbs convention not configurable via a single rule) --- eslint.config.ts | 44 ++++++- package-lock.json | 66 +++++++++++ package.json | 1 + src/app.ts | 30 ++--- src/automation/scriptRuntime.ts | 24 ++-- src/automation/scriptVm.ts | 4 +- src/automation/scriptVmFactory.ts | 18 +-- .../automation/createScriptController.ts | 7 +- .../automation/deleteScriptController.ts | 5 +- src/controller/automation/getLogController.ts | 2 +- .../automation/getScriptController.ts | 4 +- .../automation/getScriptsController.ts | 2 +- .../automation/runScriptController.ts | 2 +- src/controller/controllerInterface.ts | 5 +- src/controller/getDeviceController.ts | 2 +- .../settings/putSettingsController.ts | 4 +- src/device/attribute/boolDeviceAttribute.ts | 7 +- src/device/attribute/deviceAttribute.ts | 2 +- src/device/attribute/floatDeviceAttribute.ts | 11 +- src/device/attribute/intDeviceAttribute.ts | 10 +- .../attribute/intRangeDeviceAttribute.ts | 8 +- src/device/attribute/listDeviceAttribute.ts | 16 +-- src/device/attribute/numberDeviceAttribute.ts | 8 +- src/device/attribute/strDeviceAttribute.ts | 9 +- src/device/bleDevice.ts | 6 +- src/device/detectedDeviceOfferQueue.ts | 4 +- src/device/device.ts | 18 +-- src/device/deviceId.ts | 8 +- src/device/deviceManager.ts | 26 ++-- src/device/genericDeviceUpdater.ts | 2 +- src/device/knownDeviceRegistry.ts | 2 +- src/device/peripheralDevice.ts | 4 +- src/device/protocol/airotic/airoticDevice.ts | 6 +- .../protocol/airotic/airoticDeviceFactory.ts | 4 +- .../protocol/airotic/airoticDeviceProvider.ts | 4 +- .../protocol/airotic/airoticProtocol.ts | 2 +- .../protocol/buttplugIo/buttplugIoDevice.ts | 21 ++-- .../buttplugIo/buttplugIoDeviceFactory.ts | 11 +- .../buttplugIoWebsocketDeviceProvider.ts | 14 ++- ...uttplugIoWebsocketDeviceProviderFactory.ts | 12 +- ...trlPlusButtplugWebsocketClientConnector.ts | 6 +- src/device/protocol/deviceProtocol.ts | 25 ++-- src/device/protocol/estim2b/estim2bDevice.ts | 30 +++-- .../protocol/estim2b/estim2bDeviceFactory.ts | 10 +- .../protocol/estim2b/estim2bProtocol.ts | 50 ++++---- .../estim2b/estim2bSerialDeviceProvider.ts | 4 +- src/device/protocol/messageResponseHandler.ts | 14 +-- .../slvCtrlPlus/genericSlvCtrlPlusDevice.ts | 4 +- .../slvCtrlPlus/slvCtrlPlusDeviceFactory.ts | 8 +- .../slvCtrlPlusSerialDeviceProvider.ts | 8 +- .../protocol/slvCtrlPlus/slvCtrlProtocol.ts | 26 ++-- .../slvCtrlPlus/slvCtrlProtocolLegacy.ts | 20 ++-- .../protocol/slvCtrlPlus/slvCtrlProtocolV1.ts | 12 +- .../virtual/audio/piperVirtualDeviceConfig.ts | 4 +- .../virtual/audio/piperVirtualDeviceLogic.ts | 24 ++-- .../virtual/audio/ttsVirtualDeviceConfig.ts | 2 +- .../virtual/audio/ttsVirtualDeviceLogic.ts | 13 +- .../display/displayVirtualDeviceLogic.ts | 6 +- .../virtual/genericVirtualDeviceFactory.ts | 14 +-- .../genericVirtualDeviceLogicFactory.ts | 4 +- .../randomGeneratorVirtualDeviceLogic.ts | 9 +- src/device/protocol/virtual/virtualDevice.ts | 8 +- .../protocol/virtual/virtualDeviceFactory.ts | 5 +- .../protocol/virtual/virtualDeviceLogic.ts | 2 +- .../virtual/virtualDeviceLogicFactory.ts | 5 +- .../protocol/virtual/virtualDeviceProvider.ts | 4 +- .../virtual/virtualDeviceProviderFactory.ts | 2 +- src/device/protocol/zc95/zc95Device.ts | 49 ++++---- src/device/protocol/zc95/zc95DeviceFactory.ts | 12 +- .../protocol/zc95/zc95MessageFactory.ts | 112 +++++++++--------- src/device/protocol/zc95/zc95Protocol.ts | 24 ++-- .../protocol/zc95/zc95SerialDeviceProvider.ts | 8 +- src/device/provider/bleDeviceProvider.ts | 2 +- src/device/provider/deviceProvider.ts | 4 +- src/device/provider/deviceProviderFactory.ts | 5 +- src/device/provider/deviceProviderManager.ts | 6 +- src/device/provider/serialDeviceProvider.ts | 4 +- src/device/serializedTypes.ts | 30 ++--- src/device/transport/bleDeviceTransport.ts | 20 +++- src/device/transport/bleObserver.ts | 8 +- .../transport/deviceBidirectionalTransport.ts | 5 +- .../transport/deviceReadableTransport.ts | 5 +- .../transport/deviceWritableTransport.ts | 5 +- src/device/transport/serialPortObserver.ts | 6 +- src/device/updater/bufferedDeviceUpdater.ts | 2 +- src/device/updater/deviceUpdaterInterface.ts | 5 +- src/factory/uuidFactory.ts | 2 +- src/health/healthMetricsCollector.ts | 24 ++-- src/health/serializedTypes.ts | 40 +++---- src/logging/Logger.ts | 14 +-- src/logging/PinoLogger.ts | 2 +- src/middleware/contentTypeMiddleware.ts | 4 +- src/repository/automationScriptRepository.ts | 3 +- .../automationScriptRepositoryInterface.ts | 7 +- src/repository/deviceRepositoryInterface.ts | 7 +- .../JsonSchemaValidatorFactory.ts | 1 - src/serial/frameParser.ts | 2 +- src/serial/synchronousSerialPort.ts | 6 +- .../genericDeviceAttributeDiscriminator.ts | 2 +- .../discriminator/objectDiscriminator.ts | 2 +- src/serialization/plainToClassSerializer.ts | 2 +- src/serviceMap.ts | 109 +++++++++-------- src/serviceProvider/deviceServiceProvider.ts | 20 ++-- .../schemaValidationServiceProvider.ts | 8 +- .../settingsServiceProvider.ts | 2 +- src/settings/knownDevice.ts | 2 +- src/settings/serializedTypes.ts | 6 +- src/settings/settings.ts | 64 +++++----- src/settings/settingsManager.ts | 19 ++- src/socket/deviceUpdateHandler.ts | 2 +- src/socket/types.ts | 6 +- src/types.d.ts | 2 +- src/util/async.ts | 24 ++-- src/util/dictionary.ts | 4 +- src/util/expressUtils.ts | 6 +- src/util/latestOnlyTaskQueue.ts | 22 ++-- src/util/numbers.ts | 4 +- src/util/objects.ts | 4 +- src/util/process.ts | 19 ++- 119 files changed, 815 insertions(+), 692 deletions(-) diff --git a/eslint.config.ts b/eslint.config.ts index 384962a0..1c244213 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -3,6 +3,7 @@ import eslint from "@eslint/js"; import jsdoc from "eslint-plugin-jsdoc"; import preferArrowFunctions from "eslint-plugin-prefer-arrow-functions"; import globals from 'globals'; +import stylistic from "@stylistic/eslint-plugin"; export default [ { @@ -20,6 +21,7 @@ export default [ eslint.configs.recommended, ...tseslint.configs.strict, ...tseslint.configs.stylistic, + stylistic.configs.recommended, preferArrowFunctions.configs.all, { languageOptions: { @@ -32,7 +34,7 @@ export default [ } }, plugins: { - jsdoc, + "@jsdoc": jsdoc, }, rules: { // your overrides @@ -137,9 +139,41 @@ export default [ "@typescript-eslint/typedef": "off", "@typescript-eslint/unified-signatures": "error", "@typescript-eslint/strict-boolean-expressions": "error", + "@stylistic/indent": ["error", 4], + "@stylistic/semi": ["error", "always"], + "@stylistic/comma-dangle": ["error", "always-multiline"], + "@stylistic/brace-style": "off", + "@stylistic/member-delimiter-style": [ + "error", + { + multiline: { + delimiter: "semi", + requireLast: true, + }, + singleline: { + delimiter: "comma", + requireLast: false, + }, + }, + ], + "@stylistic/operator-linebreak": [ + "error", + "before", + { + overrides: { + "=": "after", + }, + }, + ], + "@stylistic/indent-binary-ops": ["error", 4], + "@stylistic/arrow-parens": ["error", "as-needed"], + "@stylistic/padding-line-between-statements": ["error", { + blankLine: "always", + prev: ["type", "interface", "class", "enum"], + next: "export", + }], "quotes": ["error", "single", { "allowTemplateLiterals": true }], "object-curly-spacing": ["warn", "always"], - "comma-dangle": "off", "complexity": "off", "constructor-super": "error", "dot-notation": "off", @@ -161,9 +195,9 @@ export default [ "undefined" ], "id-match": "error", - "jsdoc/check-alignment": "error", - "jsdoc/check-indentation": "error", - "jsdoc/tag-lines": "error", + "@jsdoc/check-alignment": "error", + "@jsdoc/check-indentation": "error", + "@jsdoc/tag-lines": "error", "max-classes-per-file": [ "error", 1 diff --git a/package-lock.json b/package-lock.json index f1121812..6db3eb54 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "@babel/preset-typescript": "^7.22.5", "@eslint/js": "^10.0.1", "@serialport/binding-mock": "^10.2.2", + "@stylistic/eslint-plugin": "^5.10.0", "@types/cors": "^2.8.10", "@types/express": "^5.0.6", "@types/node": "^24.1.0", @@ -3166,6 +3167,71 @@ } } }, + "node_modules/@stylistic/eslint-plugin": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.10.0.tgz", + "integrity": "sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/types": "^8.56.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.0.0 || ^10.0.0" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@stylistic/eslint-plugin/node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/@timesplinter/pimple": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/@timesplinter/pimple/-/pimple-2.1.1.tgz", diff --git a/package.json b/package.json index c062518b..a7f6ec05 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "@babel/preset-typescript": "^7.22.5", "@eslint/js": "^10.0.1", "@serialport/binding-mock": "^10.2.2", + "@stylistic/eslint-plugin": "^5.10.0", "@types/cors": "^2.8.10", "@types/express": "^5.0.6", "@types/node": "^24.1.0", diff --git a/src/app.ts b/src/app.ts index 9f6a0d89..bccbd100 100644 --- a/src/app.ts +++ b/src/app.ts @@ -28,9 +28,9 @@ import HealthServiceProvider from './serviceProvider/healthServiceProvider.js'; import { logError } from './util/error.js'; import { HealthMetricsCollectorEvent } from './health/healthMetricsCollector.js'; import { SerializedHealthMetrics } from './health/serializedTypes.js'; -import http from 'http' -import https from 'https' -import fs from 'fs' +import http from 'http'; +import https from 'https'; +import fs from 'fs'; import BaseError from 'modern-errors'; import { Server } from 'socket.io'; @@ -38,23 +38,23 @@ export type SslConfig = { port: number; keyFile: string; certFile: string; -} +}; export type AppOptions = { allowedOrigins: string[]; dataPath: string; -} +}; export type ServeResult = { httpServer: http.Server; httpsServer?: https.Server; -} +}; export type AppInstance = { websocket: WebsocketServer; serve: (httpPort: number, sslConfig?: SslConfig) => ServeResult; shutdown: () => Promise; -} +}; const configureRoutes = (app: express.Application, container: Container): void => { app.get('/devices', executeController(container, 'controller.getDevices')); @@ -77,7 +77,7 @@ const configureRoutes = (app: express.Application, container: Container): void => { const deviceManager = container.get('device.manager'); @@ -105,7 +105,7 @@ const configureWebsocket = (io: WebsocketServer, container: Container deviceUpdateHandler.handle(data)); + socket.on(WebSocketEvent.deviceUpdateReceived, data => deviceUpdateHandler.handle(data)); }); deviceManager.on(DeviceManagerEvent.deviceConnected, (device: AnyDevice) => { @@ -187,7 +187,7 @@ const getPortFromServer = (server: http.Server): number => { } return address.port; -} +}; export const createApp = (container: Container, options: AppOptions): AppInstance => { const corsOptions = buildCorsOptions(options.allowedOrigins); @@ -283,10 +283,10 @@ export const createApp = (container: Container, options: AppOptions) } try { - await container.get('device.provider.manager').stopProviders(); - } catch (e: unknown) { - logError(logger, 'Failed to stop device providers during shutdown', e); - } + await container.get('device.provider.manager').stopProviders(); + } catch (e: unknown) { + logError(logger, 'Failed to stop device providers during shutdown', e); + } container.get('health.metricsCollector').stop(); @@ -302,4 +302,4 @@ export const createApp = (container: Container, options: AppOptions) } }, }; -} +}; diff --git a/src/automation/scriptRuntime.ts b/src/automation/scriptRuntime.ts index 4aa5578f..0306778f 100644 --- a/src/automation/scriptRuntime.ts +++ b/src/automation/scriptRuntime.ts @@ -9,14 +9,14 @@ import ScriptVmFactory, { deviceToBridgeJson } from './scriptVmFactory.js'; import ScriptVm from './scriptVm.js'; export type SupportedDeviceEvent = - | { type: DeviceManagerEvent.deviceConnected | DeviceManagerEvent.deviceDisconnected | DeviceManagerEvent.deviceRefreshed; device: AnyDevice; args: [] } - | { type: DeviceManagerEvent.deviceNotification; device: AnyDevice; args: [notification: DeviceNotification] }; + | { type: DeviceManagerEvent.deviceConnected | DeviceManagerEvent.deviceDisconnected | DeviceManagerEvent.deviceRefreshed, device: AnyDevice, args: [] } + | { type: DeviceManagerEvent.deviceNotification, device: AnyDevice, args: [notification: DeviceNotification] }; type ScriptRuntimeEvents = { [AutomationEventType.consoleLog]: (data: string) => void; [AutomationEventType.scriptStarted]: () => void; [AutomationEventType.scriptStopped]: () => void; -} +}; const AUTOMATION_LOG_FILENAME = 'automation.log'; @@ -26,7 +26,7 @@ export default class ScriptRuntime private readonly scriptVmFactory: ScriptVmFactory; - private vm: ScriptVm|null = null; + private vm: ScriptVm | null = null; // Distinct from `vm !== null`: cleared immediately when stop() begins so new/in-flight // runForEvent() calls bail out right away, even though `vm` itself stays alive a little @@ -37,9 +37,9 @@ export default class ScriptRuntime private readonly logger: Logger; - private logWriter: WriteStream|null = null; + private logWriter: WriteStream | null = null; - private runningSince: Date|null = null; + private runningSince: Date | null = null; private eventQueue: (() => Promise)[] = []; @@ -59,7 +59,7 @@ export default class ScriptRuntime let vm: ScriptVm; try { - vm = await this.scriptVmFactory.create(scriptCode, (message) => { + vm = await this.scriptVmFactory.create(scriptCode, message => { this.log(message); this.eventEmitter.emit(AutomationEventType.consoleLog, message); }); @@ -195,7 +195,7 @@ export default class ScriptRuntime return null !== this.runningSince; } - public getRunningSince(): Date|null + public getRunningSince(): Date | null { return this.runningSince; } @@ -218,14 +218,14 @@ export default class ScriptRuntime private async openLogWriter(filePath: string): Promise { const writer = fs.createWriteStream(filePath); - writer.on('error', (err) => { + writer.on('error', err => { this.logger.error(`Automation log write error: ${err.message}`); }); try { await new Promise((resolve, reject) => { writer.once('open', () => resolve()); - writer.once('error', (err) => reject(err)); + writer.once('error', err => reject(err)); }); } catch (e) { writer.destroy(); @@ -235,13 +235,13 @@ export default class ScriptRuntime return writer; } - public on (event: E, listener: ScriptRuntimeEvents[E]): this + public on(event: E, listener: ScriptRuntimeEvents[E]): this { this.eventEmitter.on(event, listener); return this; } - public off (event: E, listener: ScriptRuntimeEvents[E]): this + public off(event: E, listener: ScriptRuntimeEvents[E]): this { this.eventEmitter.off(event, listener); return this; diff --git a/src/automation/scriptVm.ts b/src/automation/scriptVm.ts index b9c3ca5b..b84e79f8 100644 --- a/src/automation/scriptVm.ts +++ b/src/automation/scriptVm.ts @@ -4,7 +4,7 @@ import { EventEmitter } from 'events'; export type ScriptVmSignalEvents = { eventDone: [errMsg: string | null]; lifecycleDone: [errMsg: string | null]; -} +}; export const LIFECYCLE_START = 'start'; export const LIFECYCLE_STOP = 'stop'; @@ -81,7 +81,7 @@ export default class ScriptVm private waitFor(channel: keyof ScriptVmSignalEvents): Promise { return new Promise((resolve, reject) => { - this.signals.once(channel, (errMsg) => { + this.signals.once(channel, errMsg => { if (errMsg !== null) reject(new Error(errMsg)); else resolve(); }); diff --git a/src/automation/scriptVmFactory.ts b/src/automation/scriptVmFactory.ts index ae3709b1..4bb0635b 100644 --- a/src/automation/scriptVmFactory.ts +++ b/src/automation/scriptVmFactory.ts @@ -10,7 +10,7 @@ import ScriptVm, { LIFECYCLE_START, ScriptVmSignalEvents } from './scriptVm.js'; export type BridgeDevice = { id: string; name: string; -} +}; export const toBridgeDevice = (device: AnyDevice): BridgeDevice => { return { id: device.getDeviceId, name: device.getDeviceName }; @@ -203,12 +203,12 @@ export default class ScriptVmFactory const jail = vmContext.global; const loggerMethods: Record void> = { - log: (msg) => this.automationScriptLogger.info(msg), - error: (msg) => this.automationScriptLogger.error(msg), - warn: (msg) => this.automationScriptLogger.warn(msg), - info: (msg) => this.automationScriptLogger.info(msg), - debug: (msg) => this.automationScriptLogger.debug(msg), - trace: (msg) => this.automationScriptLogger.trace(msg), + log: msg => this.automationScriptLogger.info(msg), + error: msg => this.automationScriptLogger.error(msg), + warn: msg => this.automationScriptLogger.warn(msg), + info: msg => this.automationScriptLogger.info(msg), + debug: msg => this.automationScriptLogger.debug(msg), + trace: msg => this.automationScriptLogger.trace(msg), }; await jail.set(VM_REF_LOG, new ivm.Reference((level: string, msg: string) => { @@ -218,7 +218,7 @@ export default class ScriptVmFactory })); await jail.set(VM_REF_GET_ATTRIBUTE, new ivm.Reference(async (deviceId: string, attrName: string): Promise => { - const dev = this.deviceRepository.getById(deviceId) + const dev = this.deviceRepository.getById(deviceId); if (dev === null) return null; const attr = await dev.getAttribute(attrName); if (attr === undefined) return null; @@ -232,7 +232,7 @@ export default class ScriptVmFactory })); await jail.set(VM_REF_SET_ATTRIBUTE, new ivm.Reference(async (deviceId: string, attrName: string, value: AttributeValue): Promise => { - const dev = this.deviceRepository.getById(deviceId) + const dev = this.deviceRepository.getById(deviceId); if (dev === null) throw new Error(`Device not found: ${deviceId}`); await dev.setAttribute(attrName, value); })); diff --git a/src/controller/automation/createScriptController.ts b/src/controller/automation/createScriptController.ts index 6c6fa252..fc19d365 100644 --- a/src/controller/automation/createScriptController.ts +++ b/src/controller/automation/createScriptController.ts @@ -3,10 +3,9 @@ import ControllerInterface from '../controllerInterface.js'; import AutomationScriptRepositoryInterface from '../../repository/automationScriptRepositoryInterface.js'; import { isValidAutomationScriptFileName } from '../../automation/utils.js'; - type RequestParams = { fileName: string; -} +}; type RequestBody = string; type CreateScriptRequest = Request; @@ -15,7 +14,7 @@ export default class CreateScriptController implements ControllerInterface private readonly automationScriptRepository: AutomationScriptRepositoryInterface; public constructor( - automationScriptRepository: AutomationScriptRepositoryInterface + automationScriptRepository: AutomationScriptRepositoryInterface, ) { this.automationScriptRepository = automationScriptRepository; } @@ -24,7 +23,7 @@ export default class CreateScriptController implements ControllerInterface { const matchedContentType = req.is('text/plain'); - if(false === matchedContentType || null === matchedContentType) { + if (false === matchedContentType || null === matchedContentType) { res.status(400).send('Content-Type header must be text/plain'); return; } diff --git a/src/controller/automation/deleteScriptController.ts b/src/controller/automation/deleteScriptController.ts index ad7292f9..2835d62b 100644 --- a/src/controller/automation/deleteScriptController.ts +++ b/src/controller/automation/deleteScriptController.ts @@ -3,8 +3,7 @@ import ControllerInterface from '../controllerInterface.js'; import AutomationScriptRepositoryInterface from '../../repository/automationScriptRepositoryInterface.js'; import { isValidAutomationScriptFileName } from '../../automation/utils.js'; - -type RequestParams = { fileName: string } +type RequestParams = { fileName: string }; type DeleteScriptRequest = Request; export default class DeleteScriptController implements ControllerInterface @@ -12,7 +11,7 @@ export default class DeleteScriptController implements ControllerInterface private readonly automationScriptRepository: AutomationScriptRepositoryInterface; public constructor( - automationScriptRepository: AutomationScriptRepositoryInterface + automationScriptRepository: AutomationScriptRepositoryInterface, ) { this.automationScriptRepository = automationScriptRepository; } diff --git a/src/controller/automation/getLogController.ts b/src/controller/automation/getLogController.ts index 3510a1a8..d13f359f 100644 --- a/src/controller/automation/getLogController.ts +++ b/src/controller/automation/getLogController.ts @@ -17,7 +17,7 @@ export default class GetLogController implements ControllerInterface const maxLogLines = Number(req.query.limit) || 500; try { - const lines = await this.scriptRuntime.getLog(maxLogLines) + const lines = await this.scriptRuntime.getLog(maxLogLines); res.header('Content-Type', 'text/plain').status(200).end(lines); } catch (e: unknown) { diff --git a/src/controller/automation/getScriptController.ts b/src/controller/automation/getScriptController.ts index dbce19c1..fdb5d13d 100644 --- a/src/controller/automation/getScriptController.ts +++ b/src/controller/automation/getScriptController.ts @@ -5,7 +5,7 @@ import { isValidAutomationScriptFileName } from '../../automation/utils.js'; type RequestParams = { fileName: string; -} +}; type GetScriptRequest = Request; @@ -14,7 +14,7 @@ export default class GetScriptController implements ControllerInterface private readonly automationScriptRepository: AutomationScriptRepositoryInterface; public constructor( - automationScriptRepository: AutomationScriptRepositoryInterface + automationScriptRepository: AutomationScriptRepositoryInterface, ) { this.automationScriptRepository = automationScriptRepository; } diff --git a/src/controller/automation/getScriptsController.ts b/src/controller/automation/getScriptsController.ts index 3396229f..9ac2ab27 100644 --- a/src/controller/automation/getScriptsController.ts +++ b/src/controller/automation/getScriptsController.ts @@ -13,7 +13,7 @@ export default class GetScriptsController implements ControllerInterface public constructor( automationScriptRepository: AutomationScriptRepositoryInterface, - serializer: ClassToPlainSerializer + serializer: ClassToPlainSerializer, ) { this.automationScriptRepository = automationScriptRepository; this.serializer = serializer; diff --git a/src/controller/automation/runScriptController.ts b/src/controller/automation/runScriptController.ts index b347724d..18dba7a2 100644 --- a/src/controller/automation/runScriptController.ts +++ b/src/controller/automation/runScriptController.ts @@ -17,7 +17,7 @@ export default class RunScriptController implements ControllerInterface { const matchedContentType = req.is('text/plain'); - if(false === matchedContentType || null === matchedContentType) { + if (false === matchedContentType || null === matchedContentType) { res.status(400).send('Content-Type header must be text/plain'); return; } diff --git a/src/controller/controllerInterface.ts b/src/controller/controllerInterface.ts index 11be4e89..15559bf1 100644 --- a/src/controller/controllerInterface.ts +++ b/src/controller/controllerInterface.ts @@ -2,5 +2,6 @@ import { Request, Response } from 'express'; type ControllerInterface = { execute(req: Request, res: Response): void | Promise; -} -export default ControllerInterface +}; + +export default ControllerInterface; diff --git a/src/controller/getDeviceController.ts b/src/controller/getDeviceController.ts index e563d57f..1f4d7025 100644 --- a/src/controller/getDeviceController.ts +++ b/src/controller/getDeviceController.ts @@ -31,7 +31,7 @@ export default class GetDeviceController implements ControllerInterface res.json(this.serializer.transform( device, - deviceDiscriminator.createClassTransformerTypeDiscriminator('type') + deviceDiscriminator.createClassTransformerTypeDiscriminator('type'), )); } } diff --git a/src/controller/settings/putSettingsController.ts b/src/controller/settings/putSettingsController.ts index 50a857ba..0dece059 100644 --- a/src/controller/settings/putSettingsController.ts +++ b/src/controller/settings/putSettingsController.ts @@ -20,7 +20,7 @@ export default class PutSettingsController implements ControllerInterface public constructor( settingsManager: SettingsManager, classToPlainSerializer: ClassToPlainSerializer, - plainToClassSerializer: PlainToClassSerializer + plainToClassSerializer: PlainToClassSerializer, ) { this.settingsManager = settingsManager; this.plainToClassSerializer = plainToClassSerializer; @@ -40,7 +40,7 @@ export default class PutSettingsController implements ControllerInterface res.status(400).json({ message: `Settings are not in a valid format`, - errors: e.validationErrors + errors: e.validationErrors, }); return; } diff --git a/src/device/attribute/boolDeviceAttribute.ts b/src/device/attribute/boolDeviceAttribute.ts index 398c42f2..7506d6a4 100644 --- a/src/device/attribute/boolDeviceAttribute.ts +++ b/src/device/attribute/boolDeviceAttribute.ts @@ -1,15 +1,16 @@ import DeviceAttribute, { DeviceAttributeModifier, NotJustUndefined, NotUndefined } from './deviceAttribute.js'; type BoolDeviceAttributeValue = NotJustUndefined; -export type InitializedBoolDeviceAttribute = BoolDeviceAttribute -export default class BoolDeviceAttribute extends DeviceAttribute { +export type InitializedBoolDeviceAttribute = BoolDeviceAttribute; +export default class BoolDeviceAttribute extends DeviceAttribute +{ public static createInitialized( name: string, label: string | undefined, modifier: DeviceAttributeModifier, - initialValue: boolean + initialValue: boolean, ): InitializedBoolDeviceAttribute { return new BoolDeviceAttribute(name, label, modifier, initialValue); } diff --git a/src/device/attribute/deviceAttribute.ts b/src/device/attribute/deviceAttribute.ts index e9aacb7e..35863987 100644 --- a/src/device/attribute/deviceAttribute.ts +++ b/src/device/attribute/deviceAttribute.ts @@ -9,7 +9,7 @@ export enum DeviceAttributeModifier { readOnly = 'ro', readWrite = 'rw', - writeOnly = 'wo' + writeOnly = 'wo', } @Exclude() diff --git a/src/device/attribute/floatDeviceAttribute.ts b/src/device/attribute/floatDeviceAttribute.ts index bf9e9a0f..61b89768 100644 --- a/src/device/attribute/floatDeviceAttribute.ts +++ b/src/device/attribute/floatDeviceAttribute.ts @@ -3,16 +3,17 @@ import { Float } from '../../util/numbers.js'; import NumberDeviceAttribute from './numberDeviceAttribute.js'; type FloatDeviceAttributeValue = NotJustUndefined; -export type InitializedFloatGenericDeviceAttribute = FloatDeviceAttribute; -export default class FloatDeviceAttribute extends NumberDeviceAttribute { +export type InitializedFloatGenericDeviceAttribute = FloatDeviceAttribute; +export default class FloatDeviceAttribute extends NumberDeviceAttribute +{ public constructor( name: string, label: string | undefined, modifier: DeviceAttributeModifier, uom: string | undefined, - initialValue: T + initialValue: T, ) { super(name, label, modifier, uom, initialValue); } @@ -22,7 +23,7 @@ export default class FloatDeviceAttribute(name, label, modifier, uom, initialValue); } @@ -31,7 +32,7 @@ export default class FloatDeviceAttribute; -export type InitializedIntGenericDeviceAttribute = IntDeviceAttribute - -export default class IntDeviceAttribute extends NumberDeviceAttribute { +export type InitializedIntGenericDeviceAttribute = IntDeviceAttribute; +export default class IntDeviceAttribute extends NumberDeviceAttribute +{ public static createInitialized( name: string, label: string | undefined, modifier: DeviceAttributeModifier, uom: string | undefined, - initialValue: Int + initialValue: Int, ): InitializedIntGenericDeviceAttribute { return new IntDeviceAttribute(name, label, modifier, uom, initialValue); } @@ -21,7 +21,7 @@ export default class IntDeviceAttribute; -export default class IntRangeDeviceAttribute extends NumberDeviceAttribute { +export default class IntRangeDeviceAttribute extends NumberDeviceAttribute +{ @Expose({ name: 'min' }) private _min: Int; @@ -31,7 +32,7 @@ export default class IntRangeDeviceAttribute(name, label, modifier, uom, min, max, incrementStep, initialValue); } @@ -43,7 +44,7 @@ export default class IntRangeDeviceAttribute = ListDeviceAttribute; -export type ListDeviceAttributeOption = { key: IKey, value: IValue } +export type ListDeviceAttributeOption = { key: IKey, value: IValue }; export type ListDeviceAttributeOptions = ListDeviceAttributeOption[]; export default class ListDeviceAttribute< IKey extends ListDeviceAttributeItem, IValue extends ListDeviceAttributeItem, - V extends IKey | undefined = IKey | undefined + V extends IKey | undefined = IKey | undefined, > extends DeviceAttribute { @Expose({ name: 'values' }) @@ -25,7 +25,7 @@ export default class ListDeviceAttribute< label: string | undefined, modifier: DeviceAttributeModifier, values: ListDeviceAttributeOptions, - initialValue: V + initialValue: V, ) { super(name, label, modifier, initialValue); @@ -37,10 +37,10 @@ export default class ListDeviceAttribute< label: string | undefined, modifier: DeviceAttributeModifier, values: ListDeviceAttributeOptions, - initialValue: IKey + initialValue: IKey, ): InitializedListDeviceAttribute { return new ListDeviceAttribute( - name, label, modifier, values, initialValue + name, label, modifier, values, initialValue, ); } @@ -48,10 +48,10 @@ export default class ListDeviceAttribute< name: string, label: string | undefined, modifier: DeviceAttributeModifier, - values: ListDeviceAttributeOptions + values: ListDeviceAttributeOptions, ): ListDeviceAttribute { return new ListDeviceAttribute( - name, label, modifier, values, undefined + name, label, modifier, values, undefined, ); } diff --git a/src/device/attribute/numberDeviceAttribute.ts b/src/device/attribute/numberDeviceAttribute.ts index c3d10a81..5e163133 100644 --- a/src/device/attribute/numberDeviceAttribute.ts +++ b/src/device/attribute/numberDeviceAttribute.ts @@ -4,17 +4,17 @@ import { Float, Int } from '../../util/numbers.js'; export type NumberAttributeValue = NotJustUndefined; -export default abstract class NumberDeviceAttribute extends DeviceAttribute { - +export default abstract class NumberDeviceAttribute extends DeviceAttribute +{ @Expose({ name: 'uom' }) - private readonly _uom: string|undefined; + private readonly _uom: string | undefined; public constructor( name: string, label: string | undefined, modifier: DeviceAttributeModifier, uom: string | undefined, - initialValue: T + initialValue: T, ) { super(name, label, modifier, initialValue); this._uom = uom; diff --git a/src/device/attribute/strDeviceAttribute.ts b/src/device/attribute/strDeviceAttribute.ts index a3daac90..38d71117 100644 --- a/src/device/attribute/strDeviceAttribute.ts +++ b/src/device/attribute/strDeviceAttribute.ts @@ -1,15 +1,16 @@ import DeviceAttribute, { DeviceAttributeModifier, NotJustUndefined, NotUndefined } from './deviceAttribute.js'; type StrDeviceAttributeValue = NotJustUndefined; -export type InitializedStrDeviceAttribute = StrDeviceAttribute; -export default class StrDeviceAttribute extends DeviceAttribute { +export type InitializedStrDeviceAttribute = StrDeviceAttribute; +export default class StrDeviceAttribute extends DeviceAttribute +{ public static createInitialized( name: string, label: string | undefined, modifier: DeviceAttributeModifier, - initialValue: string + initialValue: string, ): InitializedStrDeviceAttribute { return new StrDeviceAttribute(name, label, modifier, initialValue); } @@ -18,7 +19,7 @@ export default class StrDeviceAttribute; export default abstract class BleDevice< TAttributes extends DeviceAttributes = DeviceAttributes, TNotifications extends DeviceNotifications = NoDeviceNotifications, - TConfig extends AnyDeviceConfig = NoDeviceConfig + TConfig extends AnyDeviceConfig = NoDeviceConfig, > extends Device { private readonly peripheral: Peripheral; @@ -45,7 +45,7 @@ export default abstract class BleDevice< this.rssiInterval = setInterval(asyncHandler( async () => await this.requestRssiUpdate(), - (e: unknown) => logError(this.logger,`Error during RSSI update for device ${this.deviceId}`, e) + (e: unknown) => logError(this.logger, `Error during RSSI update for device ${this.deviceId}`, e), ), 5000); this.reconnectHandler = asyncHandler( @@ -64,7 +64,7 @@ export default abstract class BleDevice< await this.close(); } }, - (err: unknown) => logError(this.logger, `Error in reconnect handler for device ${this.deviceId}`, err) + (err: unknown) => logError(this.logger, `Error in reconnect handler for device ${this.deviceId}`, err), ); this.peripheral.on('disconnect', this.reconnectHandler); diff --git a/src/device/detectedDeviceOfferQueue.ts b/src/device/detectedDeviceOfferQueue.ts index 13677ef7..af4f2205 100644 --- a/src/device/detectedDeviceOfferQueue.ts +++ b/src/device/detectedDeviceOfferQueue.ts @@ -75,13 +75,13 @@ export default class DetectedDeviceOfferQueue (reason: unknown): OfferResult => ({ successful: false, reason: reason, - }) + }), ); } private async runOffer( deviceOffer: DeviceOffer, - cancellationToken: CancellationToken + cancellationToken: CancellationToken, ): Promise> { const device = await deviceOffer(cancellationToken); diff --git a/src/device/device.ts b/src/device/device.ts index 4259df04..2401a38c 100644 --- a/src/device/device.ts +++ b/src/device/device.ts @@ -17,10 +17,10 @@ export type AnyDeviceNotifications = JsonObject; export type AttributeKeyOf = keyof A & string; export type AttributeValueOf> = - NonNullable['value']; + NonNullable['value']; export type DeviceAttributeOf = { - [K in AttributeKeyOf]: T[K] & { name: K } + [K in AttributeKeyOf]: T[K] & { name: K } }[AttributeKeyOf]; export type DeviceData = { @@ -30,7 +30,7 @@ export type DeviceData = { export type DeviceError = { reason: string; occurredAt: Date; -} +}; export enum DeviceEvent { deviceDisconnected = 'deviceDisconnected', @@ -39,16 +39,16 @@ export enum DeviceEvent { } export type DeviceNotification = - { [K in keyof TNotifications & string]: { type: K; data: TNotifications[K] } }[keyof TNotifications & string]; + { [K in keyof TNotifications & string]: { type: K, data: TNotifications[K] } }[keyof TNotifications & string]; export type DeviceEventMap< TDevice extends Device = Device, - TNotifications extends DeviceNotifications = AnyDeviceNotifications + TNotifications extends DeviceNotifications = AnyDeviceNotifications, > = { [DeviceEvent.deviceRefreshed]: [device: TDevice]; [DeviceEvent.deviceDisconnected]: [device: TDevice]; [DeviceEvent.deviceNotification]: [device: TDevice, notification: DeviceNotification]; -} +}; export type WithUntypedAttributes> = Omit & { setAttribute(attributeName: string, value: AttributeValue): Promise; @@ -60,7 +60,7 @@ export type AnyDevice = WithUntypedAttributes; export default abstract class Device< TAttributes extends DeviceAttributes = DeviceAttributes, TNotifications extends DeviceNotifications = NoDeviceNotifications, - TConfig extends AnyDeviceConfig = NoDeviceConfig + TConfig extends AnyDeviceConfig = NoDeviceConfig, > { @Expose() protected readonly deviceId: DeviceId; @@ -110,7 +110,7 @@ export default abstract class Device< attributes: TAttributes, config: TConfig, eventEmitter: EventEmitter, - logger: Logger + logger: Logger, ) { this.deviceId = deviceId; this.deviceName = deviceName; @@ -222,7 +222,7 @@ export default abstract class Device< } protected isAttributePresent( - attr: TAttributes[keyof TAttributes] + attr: TAttributes[keyof TAttributes], ): attr is DeviceAttributeOf { return attr !== null && typeof attr === 'object' && 'name' in attr && Object.keys(this.attributes).includes(attr.name); } diff --git a/src/device/deviceId.ts b/src/device/deviceId.ts index d55d0802..361f541f 100644 --- a/src/device/deviceId.ts +++ b/src/device/deviceId.ts @@ -4,8 +4,8 @@ const DEVICE_NAMESPACE = '1e0758c9-799d-40b5-b2fc-63f1e66afb76'; const deviceIdSymbol = Symbol(); const detectionIdSymbol = Symbol(); -export type DeviceId = string & { [deviceIdSymbol]: never } -export type DetectionId = string & { [detectionIdSymbol]: never } +export type DeviceId = string & { [deviceIdSymbol]: never }; +export type DetectionId = string & { [detectionIdSymbol]: never }; export const DeviceId = { create: (seed: string): DeviceId => { @@ -16,7 +16,7 @@ export const DeviceId = { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions return detectionId as unknown as DeviceId; }, -} +}; export const DetectionId = { create: (seed: string): DetectionId => { @@ -27,4 +27,4 @@ export const DetectionId = { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions return deviceId as unknown as DetectionId; }, -} +}; diff --git a/src/device/deviceManager.ts b/src/device/deviceManager.ts index 9c1f4e03..cfaed464 100644 --- a/src/device/deviceManager.ts +++ b/src/device/deviceManager.ts @@ -13,7 +13,7 @@ import DetectedDeviceOfferQueue, { OfferResult } from './detectedDeviceOfferQueu export type DeviceDetectionInfo = { type: string; detectionId: DetectionId; -} +}; export enum DeviceManagerEvent { deviceConnected = 'deviceConnected', @@ -27,7 +27,7 @@ type DisabledDetectedDevice = { deviceDetectionInfo: DeviceDetectionInfo; canonicalId: DeviceId; deviceReleased: Promise; -} +}; type DeviceManagerEventMap = { [DeviceManagerEvent.deviceConnected]: [device: AnyDevice]; @@ -35,9 +35,9 @@ type DeviceManagerEventMap = { [DeviceManagerEvent.deviceRefreshed]: [device: AnyDevice]; [DeviceManagerEvent.deviceDetected]: [deviceDetectionInfo: DeviceDetectionInfo]; [DeviceManagerEvent.deviceNotification]: [device: AnyDevice, notification: DeviceNotification]; -} +}; -type ConnectedDevice = { device: AnyDevice, deviceDetectionInfo: DeviceDetectionInfo } +type ConnectedDevice = { device: AnyDevice, deviceDetectionInfo: DeviceDetectionInfo }; export default class DeviceManager { @@ -59,7 +59,7 @@ export default class DeviceManager public constructor( eventEmitter: EventEmitter, settingsManager: SettingsManager, - logger: Logger + logger: Logger, ) { this.eventEmitter = eventEmitter; this.logger = logger.child({ name: DeviceManager.name }); @@ -112,7 +112,7 @@ export default class DeviceManager this.detectedDisabledDevices.delete(deviceDetectionInfo.detectionId); this.offerQueue.revoke( deviceDetectionInfo.detectionId, - new DeviceOfferRejectedError('Device has disappeared') + new DeviceOfferRejectedError('Device has disappeared'), ); } @@ -122,7 +122,7 @@ export default class DeviceManager return { successful: false, reason: new DeviceOfferRejectedError('Device is already connected') }; } - const result = await this.offerQueue.offer(deviceDetectionInfo, async (cancellationToken) => { + const result = await this.offerQueue.offer(deviceDetectionInfo, async cancellationToken => { const device = await deviceOffer(); if (true === cancellationToken.cancelled) { @@ -159,8 +159,8 @@ export default class DeviceManager private registerDevice(device: AnyDevice, deviceDetectionInfo: DeviceDetectionInfo): void { - device.on(DeviceEvent.deviceRefreshed, (d) => this.eventEmitter.emit(DeviceManagerEvent.deviceRefreshed, d)); - device.on(DeviceEvent.deviceDisconnected, (d) => { + device.on(DeviceEvent.deviceRefreshed, d => this.eventEmitter.emit(DeviceManagerEvent.deviceRefreshed, d)); + device.on(DeviceEvent.deviceDisconnected, d => { this.connectedDevices.delete(d.getDeviceId); this.eventEmitter.emit(DeviceManagerEvent.deviceDisconnected, d); }); @@ -213,17 +213,17 @@ export default class DeviceManager public getConnectedDevices(): AnyDevice[] { - return Array.from(this.connectedDevices.values(), (entry) => entry.device); + return Array.from(this.connectedDevices.values(), entry => entry.device); } - public getConnectedDevice(deviceId: DeviceId): AnyDevice|null + public getConnectedDevice(deviceId: DeviceId): AnyDevice | null { return this.connectedDevices.get(deviceId)?.device ?? null; } public on( event: T, - listener: (...args: DeviceManagerEventMap[T]) => void + listener: (...args: DeviceManagerEventMap[T]) => void, ): void { this.eventEmitter.on(event, listener); @@ -231,7 +231,7 @@ export default class DeviceManager public off( event: T, - listener: (...args: DeviceManagerEventMap[T]) => void + listener: (...args: DeviceManagerEventMap[T]) => void, ): void { this.eventEmitter.off(event, listener); diff --git a/src/device/genericDeviceUpdater.ts b/src/device/genericDeviceUpdater.ts index 9a11c995..ef9c521f 100644 --- a/src/device/genericDeviceUpdater.ts +++ b/src/device/genericDeviceUpdater.ts @@ -33,7 +33,7 @@ export default class GenericDeviceUpdater extends AbstractDeviceUpdater try { await device.setAttribute(attrKey, attrStr); this.logger.info(`${deviceLogMsg} -> done`); - } catch(e: unknown) { + } catch (e: unknown) { hadFailure = true; logError(this.logger, `${deviceLogMsg} -> failed`, e); diff --git a/src/device/knownDeviceRegistry.ts b/src/device/knownDeviceRegistry.ts index 5b8e3c41..f36084a9 100644 --- a/src/device/knownDeviceRegistry.ts +++ b/src/device/knownDeviceRegistry.ts @@ -29,7 +29,7 @@ export default class KnownDeviceRegistry if (undefined !== knownDevice) { this.logger.warn( - `Device id ${knownDevice.id} is known, but as type '${knownDevice.type}' instead of '${type}' - treating as an unknown device` + `Device id ${knownDevice.id} is known, but as type '${knownDevice.type}' instead of '${type}' - treating as an unknown device`, ); } diff --git a/src/device/peripheralDevice.ts b/src/device/peripheralDevice.ts index dcc5eac3..5f8b151d 100644 --- a/src/device/peripheralDevice.ts +++ b/src/device/peripheralDevice.ts @@ -13,7 +13,7 @@ export default abstract class PeripheralDevice< TProtocol extends DeviceProtocol>, TAttributes extends DeviceAttributes = DeviceAttributes, TNotifications extends DeviceNotifications = NoDeviceNotifications, - TConfig extends AnyDeviceConfig = NoDeviceConfig + TConfig extends AnyDeviceConfig = NoDeviceConfig, > extends Device { protected readonly transport: BidirectionalDeviceTransport; @@ -31,7 +31,7 @@ export default abstract class PeripheralDevice< attributes: TAttributes, config: TConfig, eventEmitter: EventEmitter, - logger: Logger + logger: Logger, ) { super(deviceId, deviceName, provider, connectedSince, controllable, attributes, config, eventEmitter, logger); diff --git a/src/device/protocol/airotic/airoticDevice.ts b/src/device/protocol/airotic/airoticDevice.ts index 23bb445f..8c83d26a 100644 --- a/src/device/protocol/airotic/airoticDevice.ts +++ b/src/device/protocol/airotic/airoticDevice.ts @@ -25,7 +25,6 @@ const DEFAULT_BREATH_IN_COLOR = '255,0,128'; export type BpmTrend = 'up' | 'down' | 'stable'; - export type AiroticDeviceAttributes = { restColor: StrDeviceAttribute; breathInColor: StrDeviceAttribute; @@ -35,12 +34,11 @@ export type AiroticDeviceAttributes = { bpmTrend: StrDeviceAttribute; }; - export type AiroticDeviceNotifications = { colorChange: { colorType: 'breathInColor' | 'restColor'; }; -} +}; @Exclude() export default class AiroticDevice extends BleDevice @@ -175,7 +173,7 @@ export default class AiroticDevice extends BleDevice, - V extends AttributeValueOf + V extends AttributeValueOf, >(attributeName: K, value: V): Promise { if (attributeName === 'restColor' && value !== null && typeof value === 'string') { const { r, g, b } = this.parseColor(value); diff --git a/src/device/protocol/airotic/airoticDeviceFactory.ts b/src/device/protocol/airotic/airoticDeviceFactory.ts index a3722570..bbe6bec9 100644 --- a/src/device/protocol/airotic/airoticDeviceFactory.ts +++ b/src/device/protocol/airotic/airoticDeviceFactory.ts @@ -28,7 +28,7 @@ export default class AiroticDeviceFactory dateFactory: DateFactory, eventEmitterFactory: EventEmitterFactory, knownDeviceRegistry: KnownDeviceRegistry, - logger: Logger + logger: Logger, ) { this.dateFactory = dateFactory; this.eventEmitterFactory = eventEmitterFactory; @@ -41,7 +41,7 @@ export default class AiroticDeviceFactory peripheral: Peripheral, transport: BleUartDeviceTransport, messageResponseHandler: MessageResponseHandler, - provider: string + provider: string, ): AiroticDevice { const deviceId = DeviceId.fromDetectionId(detectionId); diff --git a/src/device/protocol/airotic/airoticDeviceProvider.ts b/src/device/protocol/airotic/airoticDeviceProvider.ts index 2a483d40..3a9a0f92 100644 --- a/src/device/protocol/airotic/airoticDeviceProvider.ts +++ b/src/device/protocol/airotic/airoticDeviceProvider.ts @@ -23,7 +23,7 @@ export default class AiroticDeviceProvider extends BleDeviceProvider { return { message: data.toString('utf8'), - } + }; } public isResponseMatchingMessage(response: string, message: AiroticProtocolMessage): boolean { diff --git a/src/device/protocol/buttplugIo/buttplugIoDevice.ts b/src/device/protocol/buttplugIo/buttplugIoDevice.ts index cf96e2ce..38590e8f 100644 --- a/src/device/protocol/buttplugIo/buttplugIoDevice.ts +++ b/src/device/protocol/buttplugIo/buttplugIoDevice.ts @@ -14,11 +14,12 @@ import { logError } from '../../../util/error.js'; type ButtplugActuatorTypeKey = `${ActuatorType}-${number}`; type ButtplugSensorTypeKey = `${SensorType}-${number}`; + export type ButtplugIoDeviceAttributeKey = ButtplugActuatorTypeKey | ButtplugSensorTypeKey; export type ButtplugIoDeviceAttributes = Record< ButtplugIoDeviceAttributeKey, - IntRangeDeviceAttribute|BoolDeviceAttribute|IntDeviceAttribute + IntRangeDeviceAttribute | BoolDeviceAttribute | IntDeviceAttribute >; type AttributeValue = AttributeValueOf; @@ -42,7 +43,7 @@ export default class ButtplugIoDevice extends Device buttplugClientDevice: ButtplugClientDevice, attributes: ButtplugIoDeviceAttributes, eventEmitter: EventEmitter, - logger: Logger + logger: Logger, ) { super(deviceId, deviceName, provider, connectedSince, true, attributes, {}, eventEmitter, logger); this.buttplugClientDevice = buttplugClientDevice; @@ -50,7 +51,7 @@ export default class ButtplugIoDevice extends Device this.deviceRemovedHandler = asyncHandler( async () => { await this.close(); }, - (e: unknown) => logError(this.logger, `Failed to close removed device '${deviceId}'`, e) + (e: unknown) => logError(this.logger, `Failed to close removed device '${deviceId}'`, e), ); this.buttplugClientDevice.on('deviceremoved', this.deviceRemovedHandler); } @@ -73,12 +74,12 @@ export default class ButtplugIoDevice extends Device } public async setAttribute< - K extends AttributeKeyOf + K extends AttributeKeyOf, >(attributeName: K, value: AttributeValue): Promise> { const attribute = this.attributes[attributeName]; if (undefined === attribute) { - throw new Error(`Attribute with name '${attributeName}' does not exist for this device`) + throw new Error(`Attribute with name '${attributeName}' does not exist for this device`); } if (attribute.modifier === DeviceAttributeModifier.readOnly) { @@ -92,7 +93,7 @@ export default class ButtplugIoDevice extends Device let valueToSend; if (IntRangeDeviceAttribute.isInstance(attribute) && attribute.isValidValue(value)) { - valueToSend = value/attribute.max; + valueToSend = value / attribute.max; } else if (BoolDeviceAttribute.isInstance(attribute) && attribute.isValidValue(value)) { valueToSend = true === value ? 1 : 0; } else if (IntDeviceAttribute.isInstance(attribute) && attribute.isValidValue(value)) { @@ -115,16 +116,16 @@ export default class ButtplugIoDevice extends Device } private isActuatorTypeKey(key: string): key is ActuatorType { - const actuatorValueSet: (ActuatorType|string)[] = Object.values(ActuatorType); + const actuatorValueSet: (ActuatorType | string)[] = Object.values(ActuatorType); return actuatorValueSet.includes(key); } protected async send(command: ActuatorType, index: number, value: number): Promise { return await this.buttplugClientDevice.scalar({ - 'ActuatorType': command, - 'Scalar': value, - 'Index': index + ActuatorType: command, + Scalar: value, + Index: index, }); } diff --git a/src/device/protocol/buttplugIo/buttplugIoDeviceFactory.ts b/src/device/protocol/buttplugIo/buttplugIoDeviceFactory.ts index fa8462a4..fac55729 100644 --- a/src/device/protocol/buttplugIo/buttplugIoDeviceFactory.ts +++ b/src/device/protocol/buttplugIo/buttplugIoDeviceFactory.ts @@ -12,7 +12,6 @@ import IntDeviceAttribute from '../../attribute/intDeviceAttribute.js'; import EventEmitterFactory from '../../../factory/eventEmitterFactory.js'; import { DeviceId, DetectionId } from '../../deviceId.js'; - export default class ButtplugIoDeviceFactory { private readonly dateFactory: DateFactory; @@ -27,7 +26,7 @@ export default class ButtplugIoDeviceFactory dateFactory: DateFactory, eventEmitterFactory: EventEmitterFactory, knownDeviceRegistry: KnownDeviceRegistry, - logger: Logger + logger: Logger, ) { this.dateFactory = dateFactory; this.eventEmitterFactory = eventEmitterFactory; @@ -77,11 +76,11 @@ export default class ButtplugIoDeviceFactory Int.ZERO, Int.from(item.StepCount), Int.from(1), - Int.ZERO + Int.ZERO, ); } else { attributes[attrName] = BoolDeviceAttribute.createInitialized( - attrName, item.FeatureDescriptor, DeviceAttributeModifier.writeOnly, false + attrName, item.FeatureDescriptor, DeviceAttributeModifier.writeOnly, false, ); } } @@ -100,7 +99,7 @@ export default class ButtplugIoDeviceFactory Int.from(item.SensorRange[0]), Int.from(item.SensorRange[1]), Int.from(1), - Int.ZERO + Int.ZERO, ); } else { attributes[attrName] = IntDeviceAttribute.createInitialized( @@ -108,7 +107,7 @@ export default class ButtplugIoDeviceFactory item.FeatureDescriptor, DeviceAttributeModifier.readOnly, undefined, - Int.ZERO + Int.ZERO, ); } } diff --git a/src/device/protocol/buttplugIo/buttplugIoWebsocketDeviceProvider.ts b/src/device/protocol/buttplugIo/buttplugIoWebsocketDeviceProvider.ts index f13357d4..dc3fb735 100644 --- a/src/device/protocol/buttplugIo/buttplugIoWebsocketDeviceProvider.ts +++ b/src/device/protocol/buttplugIo/buttplugIoWebsocketDeviceProvider.ts @@ -1,4 +1,4 @@ -import { ButtplugClientDevice, ButtplugClient, ButtplugNodeWebsocketClientConnector } from 'buttplug' +import { ButtplugClientDevice, ButtplugClient, ButtplugNodeWebsocketClientConnector } from 'buttplug'; import ButtplugIoDevice from './buttplugIoDevice.js'; import DeviceProvider from '../../provider/deviceProvider.js'; import ButtplugIoDeviceFactory from './buttplugIoDeviceFactory.js'; @@ -48,7 +48,7 @@ export default class ButtplugIoWebsocketDeviceProvider extends DeviceProvider< websocketAddress: string, autoScan: boolean, useDeviceNameAsId: boolean, - logger: Logger + logger: Logger, ) { super(deviceManager, logger.child({ name: ButtplugIoWebsocketDeviceProvider.name })); this.buttplugIoDeviceFactory = deviceFactory; @@ -67,7 +67,7 @@ export default class ButtplugIoWebsocketDeviceProvider extends DeviceProvider< this.buttplugClient.on('disconnect', asyncHandler( this.handleLostConnection.bind(this, url), - (e: unknown) => logError(this.logger, `Error in disconnect handler`, e) + (e: unknown) => logError(this.logger, `Error in disconnect handler`, e), )); this.buttplugClient.on('deviceadded', this.announceButtplugIoDevice.bind(this)); this.buttplugClient.on('deviceremoved', this.revokePendingButtplugIoDevice.bind(this)); @@ -98,7 +98,7 @@ export default class ButtplugIoWebsocketDeviceProvider extends DeviceProvider< private connectClient(): void { this.connectionIntervalRef ??= setImmediateInterval( () => void this.connectToServer(), - ButtplugIoWebsocketDeviceProvider.CONNECT_RETRY_INTERVAL_MS + ButtplugIoWebsocketDeviceProvider.CONNECT_RETRY_INTERVAL_MS, ); } @@ -117,7 +117,9 @@ export default class ButtplugIoWebsocketDeviceProvider extends DeviceProvider< this.connectionIntervalRef = undefined; if (this.autoScan) { - this.autoScanningIntervalRef ??= setImmediateInterval(() => { this.discoverButtplugIoDevices() }, ButtplugIoWebsocketDeviceProvider.AUTO_SCAN_INTERVAL_MS); + this.autoScanningIntervalRef ??= setImmediateInterval(() => { + this.discoverButtplugIoDevices(); + }, ButtplugIoWebsocketDeviceProvider.AUTO_SCAN_INTERVAL_MS); } } catch (e: unknown) { logError(this.logger, `Could not connect to buttplug.io server (${url})`, hasProperty(e, 'message') ? e.message : 'unknown'); @@ -193,7 +195,7 @@ export default class ButtplugIoWebsocketDeviceProvider extends DeviceProvider< const device = this.buttplugIoDeviceFactory.create( deviceDetectionInfo.detectionId, deviceDetectionInfo.buttplugClientDevice, - ButtplugIoWebsocketDeviceProvider.providerName + ButtplugIoWebsocketDeviceProvider.providerName, ); return Promise.resolve(device); diff --git a/src/device/protocol/buttplugIo/buttplugIoWebsocketDeviceProviderFactory.ts b/src/device/protocol/buttplugIo/buttplugIoWebsocketDeviceProviderFactory.ts index 0a0b36e7..eeed7760 100644 --- a/src/device/protocol/buttplugIo/buttplugIoWebsocketDeviceProviderFactory.ts +++ b/src/device/protocol/buttplugIo/buttplugIoWebsocketDeviceProviderFactory.ts @@ -5,10 +5,10 @@ import ButtplugIoWebsocketDeviceProvider from './buttplugIoWebsocketDeviceProvid import DeviceManager from '../../deviceManager.js'; type ButtplugIoWebsocketDeviceProviderConfig = { - address: string, - autoScan: boolean, - useDeviceNameAsId: boolean -} + address: string; + autoScan: boolean; + useDeviceNameAsId: boolean; +}; export default class ButtplugIoWebsocketDeviceProviderFactory implements DeviceProviderFactory { @@ -21,7 +21,7 @@ export default class ButtplugIoWebsocketDeviceProviderFactory implements DeviceP public constructor( deviceManager: DeviceManager, deviceFactory: ButtplugIoDeviceFactory, - logger: Logger + logger: Logger, ) { this.deviceManager = deviceManager; this.deviceFactory = deviceFactory; @@ -36,7 +36,7 @@ export default class ButtplugIoWebsocketDeviceProviderFactory implements DeviceP config.address, config.autoScan, config.useDeviceNameAsId, - this.logger + this.logger, ); } } diff --git a/src/device/protocol/buttplugIo/slvCtrlPlusButtplugWebsocketClientConnector.ts b/src/device/protocol/buttplugIo/slvCtrlPlusButtplugWebsocketClientConnector.ts index 96c3aba0..e6722cdd 100644 --- a/src/device/protocol/buttplugIo/slvCtrlPlusButtplugWebsocketClientConnector.ts +++ b/src/device/protocol/buttplugIo/slvCtrlPlusButtplugWebsocketClientConnector.ts @@ -19,7 +19,7 @@ export default class SlvCtrlPlusButtplugWebsocketClientConnector extends Buttplu this._ws = ws; try { await this.initialize(); - this._ws.addEventListener('message', (msg) => { + this._ws.addEventListener('message', msg => { this.parseIncomingMessage(msg); }); this._ws.removeEventListener('close', onCloseCallback); @@ -34,8 +34,8 @@ export default class SlvCtrlPlusButtplugWebsocketClientConnector extends Buttplu // browsers usually only throw Error Code 1006. It's up to those using this // library to state what the problem might be. - ws.addEventListener('error', onErrorCallback) + ws.addEventListener('error', onErrorCallback); ws.addEventListener('close', onCloseCallback); }); - } + }; } diff --git a/src/device/protocol/deviceProtocol.ts b/src/device/protocol/deviceProtocol.ts index bd1daa11..e4019fbb 100644 --- a/src/device/protocol/deviceProtocol.ts +++ b/src/device/protocol/deviceProtocol.ts @@ -1,5 +1,5 @@ export type ProtocolError = - | { type: 'invalid_frame'; reason: string } + | { type: 'invalid_frame', reason: string } | { type: 'checksum_failed' } | { type: 'unknown_message_type' }; @@ -9,26 +9,29 @@ export type DecodeResult = export type Message = { message: T; -} +}; export type MessageWithResponse = Message & { - responseType: R|undefined; -} + responseType: R | undefined; +}; -export type MessageWithOptionalResponse = Message|MessageWithResponse; +export type MessageWithOptionalResponse = Message | MessageWithResponse; export type InferMR

= P extends DeviceProtocol> ? T : never; -export type InferMessage = MR extends MessageWithResponse ? M : - MR extends Message ? M : never; -export type InferResponse = MR extends MessageWithResponse ? R : - MR extends Message ? undefined : never; +export type InferMessage = MR extends MessageWithResponse + ? M + : MR extends Message ? M : never; +export type InferResponse = MR extends MessageWithResponse + ? R + : MR extends Message ? undefined : never; type DeviceProtocol> = { encode(message: InferMessage): Buffer; decode(data: Buffer): DecodeResult>; isResponseMatchingMessage(response: InferResponse, message: MR): boolean; -} -export default DeviceProtocol +}; + +export default DeviceProtocol; export const getErrorFromDecodeResult = (protocolError: ProtocolError, transportResponse: Buffer): Error => { switch (protocolError.type) { diff --git a/src/device/protocol/estim2b/estim2bDevice.ts b/src/device/protocol/estim2b/estim2bDevice.ts index f12af370..88171979 100644 --- a/src/device/protocol/estim2b/estim2bDevice.ts +++ b/src/device/protocol/estim2b/estim2bDevice.ts @@ -14,17 +14,16 @@ import EventEmitter from 'events'; import Logger from '../../../logging/Logger.js'; import { DeviceId } from '../../deviceId.js'; - export type EStim2bDeviceAttributes = { - mode: ListDeviceAttribute, - channelALevel: IntRangeDeviceAttribute, - channelBLevel: IntRangeDeviceAttribute, - pulseFrequency?: IntRangeDeviceAttribute, - pulsePwm?: IntRangeDeviceAttribute, - channelsJoined: BoolDeviceAttribute, - highPowerMode: BoolDeviceAttribute, - batteryStatus: StrDeviceAttribute, -} + mode: ListDeviceAttribute; + channelALevel: IntRangeDeviceAttribute; + channelBLevel: IntRangeDeviceAttribute; + pulseFrequency?: IntRangeDeviceAttribute; + pulsePwm?: IntRangeDeviceAttribute; + channelsJoined: BoolDeviceAttribute; + highPowerMode: BoolDeviceAttribute; + batteryStatus: StrDeviceAttribute; +}; export type EStim2bBatteryStatus = 'mains' | 'full' | 'medium' | 'low' | 'critical'; @@ -33,7 +32,6 @@ type AttributeValue = AttributeValueOf< @Exclude() export default class EStim2bDevice extends PeripheralDevice { - @Expose() private readonly fwVersion: string; @@ -93,7 +91,7 @@ export default class EStim2bDevice extends PeripheralDevice + K extends AttributeKeyOf, >(attributeName: K, value: AttributeValue): Promise> { const attribute = this.attributes[attributeName]; @@ -124,7 +122,7 @@ export default class EStim2bDevice extends PeripheralDevice { const attributes = this.getAttributes(initialStatus); const knownDevice = this.knownDeviceRegistry.resolve(DeviceId.fromDetectionId(detectionId), 'estim2b', provider); @@ -73,7 +73,7 @@ export default class Estim2bDeviceFactory 'Mode', DeviceAttributeModifier.readWrite, availableModes, - Int.from(initialStatus.currentMode) + Int.from(initialStatus.currentMode), ); const channelALevel = IntRangeDeviceAttribute.createInitialized( @@ -84,7 +84,7 @@ export default class Estim2bDeviceFactory Int.ZERO, Int.from(100), Int.from(1), - Int.from(initialStatus.channelALevel) + Int.from(initialStatus.channelALevel), ); const channelBLevel = IntRangeDeviceAttribute.createInitialized( @@ -95,7 +95,7 @@ export default class Estim2bDeviceFactory Int.ZERO, Int.from(100), Int.from(1), - Int.from(initialStatus.channelBLevel) + Int.from(initialStatus.channelBLevel), ); const highPowerMode = BoolDeviceAttribute.createInitialized( diff --git a/src/device/protocol/estim2b/estim2bProtocol.ts b/src/device/protocol/estim2b/estim2bProtocol.ts index acc2e00b..0c970f07 100644 --- a/src/device/protocol/estim2b/estim2bProtocol.ts +++ b/src/device/protocol/estim2b/estim2bProtocol.ts @@ -1,16 +1,16 @@ import DeviceProtocol, { DecodeResult, InferMessage, MessageWithResponse } from '../deviceProtocol.js'; export type EStim2bStatus = { - batteryLevel: number, - channelALevel: number, - channelBLevel: number, - pulseFrequency: number, - pulsePwm: number, - currentMode: number, - powerMode: string, - channelsJoined: boolean, - firmwareVersion: string, -} + batteryLevel: number; + channelALevel: number; + channelBLevel: number; + pulseFrequency: number; + pulsePwm: number; + currentMode: number; + powerMode: string; + channelsJoined: boolean; + firmwareVersion: string; +}; export type EStim2Channel = 'A' | 'B'; export type EStim2PowerMode = 'H' | 'L'; @@ -51,7 +51,7 @@ export type Estim2bCommand = | Estim2bSetPulsePwmCommand | Estim2bPowerZeroCommand | Estim2bResetCommand - ; +; export type EStim2bProtocolMessage = MessageWithResponse; @@ -61,7 +61,7 @@ export default class EStim2bProtocol implements DeviceProtocol InferResponse */> { + public decode(data: Buffer): DecodeResult { return EStim2bProtocol.parseResponse(data.toString('utf-8')); } @@ -154,8 +154,8 @@ export default class EStim2bProtocol implements DeviceProtocol = { resolve: (response: InferResponse) => void; reject: (error: Error) => void; timeout: ReturnType; - timeoutMs: number, - pendingSince: number, -} + timeoutMs: number; + pendingSince: number; +}; export default class MessageResponseHandler

>> { @@ -70,7 +70,7 @@ export default class MessageResponseHandler

entry.timeoutMs * 0.8) { this.logger.warn( - `Slow response time (${responseTime}ms) for message: ${JSON.stringify(entry.msg.message)}` + `Slow response time (${responseTime}ms) for message: ${JSON.stringify(entry.msg.message)}`, ); } @@ -80,7 +80,7 @@ export default class MessageResponseHandler

>( - msg: T + msg: T, ): msg is Extract> { return 'responseType' in msg; } @@ -109,7 +109,7 @@ export default class MessageResponseHandler

{ this.pendingEntries.delete(entry); reject(new Error( - `Timed out (>${realTimeoutMs}ms) waiting for response for message: ${encodedMsg.toString()}` + `Timed out (>${realTimeoutMs}ms) waiting for response for message: ${encodedMsg.toString()}`, )); }, realTimeoutMs), timeoutMs: realTimeoutMs, @@ -125,4 +125,4 @@ export default class MessageResponseHandler

+ K extends AttributeKeyOf, >(attributeName: K, value: AttributeValue): Promise> { const attr = this.attributes[attributeName]; diff --git a/src/device/protocol/slvCtrlPlus/slvCtrlPlusDeviceFactory.ts b/src/device/protocol/slvCtrlPlus/slvCtrlPlusDeviceFactory.ts index 3dfe62fd..84140ffb 100644 --- a/src/device/protocol/slvCtrlPlus/slvCtrlPlusDeviceFactory.ts +++ b/src/device/protocol/slvCtrlPlus/slvCtrlPlusDeviceFactory.ts @@ -25,7 +25,7 @@ export default class SlvCtrlPlusDeviceFactory dateFactory: DateFactory, eventEmitterFactory: EventEmitterFactory, knownDeviceRegistry: KnownDeviceRegistry, - logger: Logger + logger: Logger, ) { this.dateFactory = dateFactory; this.eventEmitterFactory = eventEmitterFactory; @@ -75,7 +75,7 @@ export default class SlvCtrlPlusDeviceFactory if (decodedInfoResponse.message.result.status !== 'ok') { const reason = decodedInfoResponse.message.result.reason ?? 'unknown'; - throw new Error(`Could not retrieve device information: ${reason}`) + throw new Error(`Could not retrieve device information: ${reason}`); } const deviceInfo = decodedInfoResponse.message.data; @@ -85,7 +85,7 @@ export default class SlvCtrlPlusDeviceFactory if (Number.isNaN(fwVersion) || Number.isNaN(protocolVersion)) { throw new Error( - `Invalid version payload: fw='${deviceInfo.fw}', protocol='${deviceInfo.protocol}'` + `Invalid version payload: fw='${deviceInfo.fw}', protocol='${deviceInfo.protocol}'`, ); } @@ -106,7 +106,7 @@ export default class SlvCtrlPlusDeviceFactory if (decodedAttrResponse.message.result.status !== 'ok') { const reason = decodedAttrResponse.message.result.reason ?? 'unknown'; - throw new Error(`Could not retrieve device attributes: ${reason}`) + throw new Error(`Could not retrieve device attributes: ${reason}`); } return protocol.getAttributes(decodedAttrResponse.message.data); diff --git a/src/device/protocol/slvCtrlPlus/slvCtrlPlusSerialDeviceProvider.ts b/src/device/protocol/slvCtrlPlus/slvCtrlPlusSerialDeviceProvider.ts index c2d18a69..17f06c22 100644 --- a/src/device/protocol/slvCtrlPlus/slvCtrlPlusSerialDeviceProvider.ts +++ b/src/device/protocol/slvCtrlPlus/slvCtrlPlusSerialDeviceProvider.ts @@ -32,7 +32,7 @@ export default class SlvCtrlPlusSerialDeviceProvider extends SerialDeviceProvide serialPortObserver: SerialPortObserver, deviceFactory: SlvCtrlPlusDeviceFactory, deviceTransportFactory: SerialDeviceTransportFactory, - logger: Logger + logger: Logger, ) { super(deviceManager, serialPortFactory, serialPortObserver, logger.child({ name: SlvCtrlPlusSerialDeviceProvider.name })); this.slvCtrlPlusDeviceFactory = deviceFactory; @@ -50,7 +50,7 @@ export default class SlvCtrlPlusSerialDeviceProvider extends SerialDeviceProvide const device = await this.slvCtrlPlusDeviceFactory.create( deviceDetectionInfo.detectionId, transport, - SlvCtrlPlusSerialDeviceProvider.providerName + SlvCtrlPlusSerialDeviceProvider.providerName, ); this.logger.info(`Module detected: ${device.getDeviceModel} (${deviceDetectionInfo.portInfo.serialNumber})`); @@ -65,7 +65,7 @@ export default class SlvCtrlPlusSerialDeviceProvider extends SerialDeviceProvide try { await transport.sendAndAwaitReceive(Buffer.from(`clear`), 250); return; - } catch(e: unknown) { + } catch (e: unknown) { const error = BaseError.normalize(e); this.logger.info(`Retrying because handshake attempt ${i} failed: ${error.message}`); if (i === maxAttempts) lastError = e; @@ -88,7 +88,7 @@ export default class SlvCtrlPlusSerialDeviceProvider extends SerialDeviceProvide } const readyParser = port.pipe(new ReadyParser({ - delimiter: [SlvCtrlPlusSerialDeviceProvider.moduleReadyByte] + delimiter: [SlvCtrlPlusSerialDeviceProvider.moduleReadyByte], })); // Let's timeout if we don't receive the ready bytes for a few seconds diff --git a/src/device/protocol/slvCtrlPlus/slvCtrlProtocol.ts b/src/device/protocol/slvCtrlPlus/slvCtrlProtocol.ts index 69b68b2e..e429c645 100644 --- a/src/device/protocol/slvCtrlPlus/slvCtrlProtocol.ts +++ b/src/device/protocol/slvCtrlPlus/slvCtrlProtocol.ts @@ -2,24 +2,24 @@ import { SlvCtrlPlusDeviceAttributes } from './slvCtrlPlusDevice.js'; import DeviceProtocol, { DecodeResult, InferMessage, InferResponse, MessageWithResponse } from '../deviceProtocol.js'; export type DeviceInfo = { - deviceType: string, - fwVersion: number, - protocolVersion: number, -} + deviceType: string; + fwVersion: number; + protocolVersion: number; +}; export type KeyValuePairs = Record; export type Result = { - status: 'ok' | 'error' | 'unknown', - reason?: string, -} & Record + status: 'ok' | 'error' | 'unknown'; + reason?: string; +} & Record; export type SlvCtrlProtocolCommand = { command: string; - args: (string|number|boolean)[]; -} + args: (string | number | boolean)[]; +}; export type SlvCtrlProtocolResponse = { - command: string, - data: KeyValuePairs, - result: Result, -} + command: string; + data: KeyValuePairs; + result: Result; +}; export type SlvCtrlProtocolMessage = MessageWithResponse; diff --git a/src/device/protocol/slvCtrlPlus/slvCtrlProtocolLegacy.ts b/src/device/protocol/slvCtrlPlus/slvCtrlProtocolLegacy.ts index 734f70a3..b890606a 100644 --- a/src/device/protocol/slvCtrlPlus/slvCtrlProtocolLegacy.ts +++ b/src/device/protocol/slvCtrlPlus/slvCtrlProtocolLegacy.ts @@ -9,15 +9,15 @@ import { Int } from '../../../util/numbers.js'; import { SlvCtrlPlusDeviceAttributes } from './slvCtrlPlusDevice.js'; import SlvCtrlProtocol, { KeyValuePairs, Result, - SlvCtrlProtocolMessage + SlvCtrlProtocolMessage, } from './slvCtrlProtocol.js'; import { DecodeResult, InferMessage, InferResponse } from '../deviceProtocol.js'; type SetAttributeResponse = { - command: string, - value: string, - status: string, -} + command: string; + value: string; + status: string; +}; export type StatusResponse = Record; @@ -40,7 +40,7 @@ export default class SlvCtrlProtocolLegacy extends SlvCtrlProtocol commandArgs = command.args; } - const argsToSend = commandArgs.map(arg => (typeof arg === 'boolean'? Number(arg) : arg).toString()); + const argsToSend = commandArgs.map(arg => (typeof arg === 'boolean' ? Number(arg) : arg).toString()); const argsSuffixed = argsToSend.length > 0 ? ` ${argsToSend.join(' ')}` : ''; return Buffer.from(`${commandToSend}${argsSuffixed}`, 'utf-8'); @@ -77,12 +77,12 @@ export default class SlvCtrlProtocolLegacy extends SlvCtrlProtocol command: command, data: keyValuePairs, result: undefined === result ? { status: 'ok' } : this.parseResult(result), - } - } + }, + }; } public getAttributes(responseData: KeyValuePairs): SlvCtrlPlusDeviceAttributes { - return SlvCtrlProtocolLegacy.parseDeviceAttributes(responseData) + return SlvCtrlProtocolLegacy.parseDeviceAttributes(responseData); } private parseResult(rawResult: string): Result @@ -197,7 +197,7 @@ export default class SlvCtrlProtocolLegacy extends SlvCtrlProtocol ); } else if ((resultList = value.split('|')).length > 0) { attr = ListDeviceAttribute.create( - name, undefined, modifier, resultList.map(v => ({ key: v, value: v })) + name, undefined, modifier, resultList.map(v => ({ key: v, value: v })), ); } else { throw new Error(`Unknown attribute data type: ${value}`); diff --git a/src/device/protocol/slvCtrlPlus/slvCtrlProtocolV1.ts b/src/device/protocol/slvCtrlPlus/slvCtrlProtocolV1.ts index 3c355e45..0514a331 100644 --- a/src/device/protocol/slvCtrlPlus/slvCtrlProtocolV1.ts +++ b/src/device/protocol/slvCtrlPlus/slvCtrlProtocolV1.ts @@ -8,7 +8,7 @@ import IntDeviceAttribute from '../../attribute/intDeviceAttribute.js'; import { Int } from '../../../util/numbers.js'; import SlvCtrlProtocol, { KeyValuePairs, Result, - SlvCtrlProtocolMessage + SlvCtrlProtocolMessage, } from './slvCtrlProtocol.js'; import { DecodeResult, InferMessage, InferResponse } from '../deviceProtocol.js'; import { SlvCtrlPlusDeviceAttributes } from './slvCtrlPlusDevice.js'; @@ -22,7 +22,7 @@ export default class SlvCtrlProtocolV1 extends SlvCtrlProtocol private static readonly keyValueSeparator = ':'; public encode(command: InferMessage): Buffer { - const argsToSend = command.args.map(arg => (typeof arg === 'boolean'? Number(arg) : arg).toString()); + const argsToSend = command.args.map(arg => (typeof arg === 'boolean' ? Number(arg) : arg).toString()); const argsSuffixed = argsToSend.length > 0 ? ` ${argsToSend.join(' ')}` : ''; return Buffer.from(`${command.command}${argsSuffixed}`, 'utf-8'); @@ -94,14 +94,14 @@ export default class SlvCtrlProtocolV1 extends SlvCtrlProtocol resultList = listContent.split('|'); if ('str' === listType) { attr = ListDeviceAttribute.create( - name, undefined, modifier, resultList.map(v => ({ key: v, value: v })) + name, undefined, modifier, resultList.map(v => ({ key: v, value: v })), ); } else if ('int' === listType) { attr = ListDeviceAttribute.create( name, undefined, modifier, resultList.map(v => { const vInt = Int.from(parseInt(v, 10)); return { key: vInt, value: vInt }; - }) + }), ); } } @@ -144,8 +144,8 @@ export default class SlvCtrlProtocolV1 extends SlvCtrlProtocol command: segments[0], data: infoSegment, result: resultSegment, - } - } + }, + }; } private static parseSegment(segment: string): KeyValuePairs { diff --git a/src/device/protocol/virtual/audio/piperVirtualDeviceConfig.ts b/src/device/protocol/virtual/audio/piperVirtualDeviceConfig.ts index c067853f..42210814 100644 --- a/src/device/protocol/virtual/audio/piperVirtualDeviceConfig.ts +++ b/src/device/protocol/virtual/audio/piperVirtualDeviceConfig.ts @@ -1,8 +1,8 @@ import { Type, Static } from '@sinclair/typebox'; export const piperVirtualDeviceConfigSchema = Type.Object({ - binary: Type.Optional(Type.String()), - model: Type.String(), + binary: Type.Optional(Type.String()), + model: Type.String(), }); export type PiperVirtualDeviceConfigSchema = typeof piperVirtualDeviceConfigSchema; diff --git a/src/device/protocol/virtual/audio/piperVirtualDeviceLogic.ts b/src/device/protocol/virtual/audio/piperVirtualDeviceLogic.ts index 5a4a9ac9..3037ece3 100644 --- a/src/device/protocol/virtual/audio/piperVirtualDeviceLogic.ts +++ b/src/device/protocol/virtual/audio/piperVirtualDeviceLogic.ts @@ -16,17 +16,15 @@ import VirtualDeviceLogic from '../virtualDeviceLogic.js'; type PiperVirtualDeviceAttributes = { text: StrDeviceAttribute; queuing: BoolDeviceAttribute; -} - +}; type PiperModelMetadata = { - num_speakers?: number, - sample_width?: number, + num_speakers?: number; + sample_width?: number; audio?: { - sample_rate?: number, - } -} - + sample_rate?: number; + }; +}; export default class PiperVirtualDeviceLogic extends VirtualDeviceLogic< PiperVirtualDeviceAttributes, @@ -48,7 +46,7 @@ export default class PiperVirtualDeviceLogic extends VirtualDeviceLogic< } public async refreshData( - device: VirtualDevice + device: VirtualDevice, ): Promise { if (device.getState === DeviceState.error) { return; @@ -95,14 +93,14 @@ export default class PiperVirtualDeviceLogic extends VirtualDeviceLogic< text: StrDeviceAttribute.create( PiperVirtualDeviceLogic.textAttrName, 'Text', - DeviceAttributeModifier.writeOnly + DeviceAttributeModifier.writeOnly, ), queuing: BoolDeviceAttribute.createInitialized( PiperVirtualDeviceLogic.queuingAttrName, 'Queuing enabled', DeviceAttributeModifier.readWrite, - false + false, ), }; } @@ -126,7 +124,7 @@ export default class PiperVirtualDeviceLogic extends VirtualDeviceLogic< env: { ...process.env, PIPER_NO_PLAYER: '1' }, stdio: ['pipe', 'pipe', 'pipe'], detached: true, - } + }, ); piperProcess.stderr.on('data', (data: Buffer) => { @@ -165,7 +163,7 @@ export default class PiperVirtualDeviceLogic extends VirtualDeviceLogic< if (undefined === metadata?.sample_width) { this.logger.warn(`Bit depth missing from piper model metadata file, falling back to ${bitDepth}`); } else { - bitDepth = metadata.sample_width * 8 + bitDepth = metadata.sample_width * 8; } let sampleRate = 22050; diff --git a/src/device/protocol/virtual/audio/ttsVirtualDeviceConfig.ts b/src/device/protocol/virtual/audio/ttsVirtualDeviceConfig.ts index c650c3e4..ad30d153 100644 --- a/src/device/protocol/virtual/audio/ttsVirtualDeviceConfig.ts +++ b/src/device/protocol/virtual/audio/ttsVirtualDeviceConfig.ts @@ -1,7 +1,7 @@ import { Type, Static } from '@sinclair/typebox'; export const ttsVirtualDeviceConfigSchema = Type.Object({ - voice: Type.Optional(Type.String()), + voice: Type.Optional(Type.String()), }); export type TtsVirtualDeviceConfigSchema = typeof ttsVirtualDeviceConfigSchema; diff --git a/src/device/protocol/virtual/audio/ttsVirtualDeviceLogic.ts b/src/device/protocol/virtual/audio/ttsVirtualDeviceLogic.ts index dd85ae08..a048e009 100644 --- a/src/device/protocol/virtual/audio/ttsVirtualDeviceLogic.ts +++ b/src/device/protocol/virtual/audio/ttsVirtualDeviceLogic.ts @@ -9,13 +9,12 @@ import { Int } from '../../../../util/numbers.js'; import Logger from '../../../../logging/Logger.js'; import { TtsVirtualDeviceConfig } from './ttsVirtualDeviceConfig.js'; - type TtsVirtualDeviceAttributes = { text: StrDeviceAttribute; speaking: BoolDeviceAttribute; queuing: BoolDeviceAttribute; queueLength: IntDeviceAttribute; -} +}; export default class TtsVirtualDeviceLogic extends VirtualDeviceLogic< TtsVirtualDeviceAttributes, @@ -84,21 +83,21 @@ export default class TtsVirtualDeviceLogic extends VirtualDeviceLogic< public configureAttributes(): TtsVirtualDeviceAttributes { const textAttr = StrDeviceAttribute.create( - TtsVirtualDeviceLogic.textAttrName, 'Text', DeviceAttributeModifier.writeOnly + TtsVirtualDeviceLogic.textAttrName, 'Text', DeviceAttributeModifier.writeOnly, ); const speakingAttr = BoolDeviceAttribute.createInitialized( TtsVirtualDeviceLogic.speakingAttrName, 'Currently speaking', DeviceAttributeModifier.readOnly, - false + false, ); const queuingAttr = BoolDeviceAttribute.createInitialized( TtsVirtualDeviceLogic.queuingAttrName, 'Queuing enabled', DeviceAttributeModifier.readWrite, - false + false, ); const queueLengthAttr = IntDeviceAttribute.createInitialized( @@ -106,14 +105,14 @@ export default class TtsVirtualDeviceLogic extends VirtualDeviceLogic< 'Queue length', DeviceAttributeModifier.readOnly, undefined, - Int.ZERO + Int.ZERO, ); return { text: textAttr, speaking: speakingAttr, queuing: queuingAttr, - queueLength: queueLengthAttr + queueLength: queueLengthAttr, }; } } diff --git a/src/device/protocol/virtual/display/displayVirtualDeviceLogic.ts b/src/device/protocol/virtual/display/displayVirtualDeviceLogic.ts index 5af8d837..a9d194db 100644 --- a/src/device/protocol/virtual/display/displayVirtualDeviceLogic.ts +++ b/src/device/protocol/virtual/display/displayVirtualDeviceLogic.ts @@ -5,7 +5,7 @@ import { NoDeviceConfig } from '../../../deviceConfig.js'; type DisplayVirtualDeviceAttributes = { content: StrDeviceAttribute; -} +}; export default class DisplayVirtualDeviceLogic extends VirtualDeviceLogic { public constructor(config: NoDeviceConfig) { @@ -21,11 +21,11 @@ export default class DisplayVirtualDeviceLogic extends VirtualDeviceLogic> = T extends VirtualDeviceLogic ? C : never; type LogicFactoryAndConfigTuple, TConfigSchema extends TObject> = { - deviceLogicFactory: VirtualDeviceLogicFactory, + deviceLogicFactory: VirtualDeviceLogicFactory; deviceConfigSchema: TConfigSchema & ( Static extends ExtractConfig ? ExtractConfig extends Static ? unknown : never : never - ), -} + ); +}; export default class GenericVirtualDeviceFactory implements VirtualDeviceFactory { private readonly dateFactory: DateFactory; @@ -37,7 +37,7 @@ export default class GenericVirtualDeviceFactory implements VirtualDeviceFactory dateFactory: DateFactory, eventemitterFactory: EventEmitterFactory, jsonSchemaValidatorFactory: JsonSchemaValidatorFactory, - logger: Logger + logger: Logger, ) { this.dateFactory = dateFactory; this.eventEmitterFactory = eventemitterFactory; @@ -47,7 +47,7 @@ export default class GenericVirtualDeviceFactory implements VirtualDeviceFactory public addLogicFactory< TLogic extends VirtualDeviceLogic, - TConfigSchema extends TObject + TConfigSchema extends TObject, >( virtualDeviceLogicFactory: LogicFactoryAndConfigTuple['deviceLogicFactory'], deviceConfigSchema: LogicFactoryAndConfigTuple['deviceConfigSchema'], @@ -61,7 +61,7 @@ export default class GenericVirtualDeviceFactory implements VirtualDeviceFactory } public create(knownDevice: KnownDevice, provider: string): Promise> { - return new Promise>((resolve) => { + return new Promise>(resolve => { const factoryName = `${GenericVirtualDeviceFactory.capitalizeFirstLetter(knownDevice.type)}VirtualDeviceLogic`; const factory = this.logicFactories.get(factoryName); @@ -100,4 +100,4 @@ export default class GenericVirtualDeviceFactory implements VirtualDeviceFactory if (!str) return ''; return str.charAt(0).toUpperCase() + str.slice(1); } -} \ No newline at end of file +} diff --git a/src/device/protocol/virtual/genericVirtualDeviceLogicFactory.ts b/src/device/protocol/virtual/genericVirtualDeviceLogicFactory.ts index f971252d..f0dc659f 100644 --- a/src/device/protocol/virtual/genericVirtualDeviceLogicFactory.ts +++ b/src/device/protocol/virtual/genericVirtualDeviceLogicFactory.ts @@ -6,7 +6,7 @@ type ExtractConfig> = T extends VirtualDe type Constructor> = new (config: ExtractConfig, logger: Logger) => TDeviceLogic; export default class GenericVirtualDeviceLogicFactory< - TDeviceLogic extends VirtualDeviceLogic + TDeviceLogic extends VirtualDeviceLogic, > implements VirtualDeviceLogicFactory { private readonly ctor: Constructor; @@ -20,7 +20,7 @@ export default class GenericVirtualDeviceLogicFactory< public static from>( genericVirtualDeviceLogicLogicConstructor: Constructor, - logger: Logger + logger: Logger, ): GenericVirtualDeviceLogicFactory { return new GenericVirtualDeviceLogicFactory( genericVirtualDeviceLogicLogicConstructor, diff --git a/src/device/protocol/virtual/randomGenerator/randomGeneratorVirtualDeviceLogic.ts b/src/device/protocol/virtual/randomGenerator/randomGeneratorVirtualDeviceLogic.ts index a5d05972..5a5c37a7 100644 --- a/src/device/protocol/virtual/randomGenerator/randomGeneratorVirtualDeviceLogic.ts +++ b/src/device/protocol/virtual/randomGenerator/randomGeneratorVirtualDeviceLogic.ts @@ -5,10 +5,9 @@ import VirtualDevice from '../virtualDevice.js'; import { Int } from '../../../../util/numbers.js'; import { RandomGeneratorVirtualDeviceConfig } from './randomGeneratorVirtualDeviceConfig.js'; - type RandomGeneratorVirtualDeviceAttributes = { value: IntDeviceAttribute; -} +}; export default class RandomGeneratorVirtualDeviceLogic extends VirtualDeviceLogic< RandomGeneratorVirtualDeviceAttributes, @@ -19,7 +18,7 @@ export default class RandomGeneratorVirtualDeviceLogic extends VirtualDeviceLogi if (config.min >= config.max) { throw new Error( - `Invalid random generator config: min (${config.min}) must be less than max (${config.max})` + `Invalid random generator config: min (${config.min}) must be less than max (${config.max})`, ); } } @@ -39,11 +38,11 @@ export default class RandomGeneratorVirtualDeviceLogic extends VirtualDeviceLogi public configureAttributes(): RandomGeneratorVirtualDeviceAttributes { const valueAttr = IntDeviceAttribute.create( - 'value', 'Random number', DeviceAttributeModifier.readOnly, undefined + 'value', 'Random number', DeviceAttributeModifier.readOnly, undefined, ); return { - value: valueAttr + value: valueAttr, }; } } diff --git a/src/device/protocol/virtual/virtualDevice.ts b/src/device/protocol/virtual/virtualDevice.ts index 74c2912f..2feddb1b 100644 --- a/src/device/protocol/virtual/virtualDevice.ts +++ b/src/device/protocol/virtual/virtualDevice.ts @@ -10,7 +10,7 @@ import { DeviceId } from '../../deviceId.js'; @Exclude() export default class VirtualDevice< - TLogic extends VirtualDeviceLogic + TLogic extends VirtualDeviceLogic, > extends Device, NoDeviceNotifications, ExtractConfig> { @Expose() private deviceModel: string; @@ -32,7 +32,7 @@ export default class VirtualDevice< config: ExtractConfig, deviceLogic: TLogic, eventEmitter: EventEmitter, - logger: Logger + logger: Logger, ) { super(deviceId, deviceName, provider, connectedSince, false, deviceLogic.configureAttributes(), config, eventEmitter, logger); @@ -66,7 +66,7 @@ export default class VirtualDevice< public async setAttribute< K extends AttributeKeyOf>, - V extends AttributeValueOf, K> + V extends AttributeValueOf, K>, >(attributeName: K, value: V): Promise { return new Promise((resolve, reject) => { this.state = DeviceState.busy; @@ -75,7 +75,7 @@ export default class VirtualDevice< if (undefined === attribute || null === attribute) { reject(new Error( - `Attribute named "${attributeName}" does not exist for device with id "${this.deviceId}"` + `Attribute named "${attributeName}" does not exist for device with id "${this.deviceId}"`, )); return; } diff --git a/src/device/protocol/virtual/virtualDeviceFactory.ts b/src/device/protocol/virtual/virtualDeviceFactory.ts index ab71af77..a99f3fc4 100644 --- a/src/device/protocol/virtual/virtualDeviceFactory.ts +++ b/src/device/protocol/virtual/virtualDeviceFactory.ts @@ -3,5 +3,6 @@ import VirtualDevice from './virtualDevice.js'; type VirtualDeviceFactory = { create(knownDevice: KnownDevice, provider: string): Promise>; -} -export default VirtualDeviceFactory +}; + +export default VirtualDeviceFactory; diff --git a/src/device/protocol/virtual/virtualDeviceLogic.ts b/src/device/protocol/virtual/virtualDeviceLogic.ts index b56b084c..5977c00d 100644 --- a/src/device/protocol/virtual/virtualDeviceLogic.ts +++ b/src/device/protocol/virtual/virtualDeviceLogic.ts @@ -14,7 +14,7 @@ export type ExtractConfig { protected config: TConfig; diff --git a/src/device/protocol/virtual/virtualDeviceLogicFactory.ts b/src/device/protocol/virtual/virtualDeviceLogicFactory.ts index 937728f6..4f9a2130 100644 --- a/src/device/protocol/virtual/virtualDeviceLogicFactory.ts +++ b/src/device/protocol/virtual/virtualDeviceLogicFactory.ts @@ -6,5 +6,6 @@ type VirtualDeviceLogicFactory create(config: ExtractConfig): TDeviceLogic; forDeviceType(): string; -} -export default VirtualDeviceLogicFactory +}; + +export default VirtualDeviceLogicFactory; diff --git a/src/device/protocol/virtual/virtualDeviceProvider.ts b/src/device/protocol/virtual/virtualDeviceProvider.ts index 92e1bb83..3d722c65 100644 --- a/src/device/protocol/virtual/virtualDeviceProvider.ts +++ b/src/device/protocol/virtual/virtualDeviceProvider.ts @@ -31,7 +31,7 @@ export default class VirtualDeviceProvider extends DeviceProvider => this.discoverVirtualDevices(), - (e: unknown) => logError(this.logger, 'Error while scanning for virtual devices after a settings change', e) + (e: unknown) => logError(this.logger, 'Error while scanning for virtual devices after a settings change', e), ); } diff --git a/src/device/protocol/virtual/virtualDeviceProviderFactory.ts b/src/device/protocol/virtual/virtualDeviceProviderFactory.ts index 398d219d..ad12b9f9 100644 --- a/src/device/protocol/virtual/virtualDeviceProviderFactory.ts +++ b/src/device/protocol/virtual/virtualDeviceProviderFactory.ts @@ -19,7 +19,7 @@ export default class VirtualDeviceProviderFactory implements DeviceProviderFacto deviceManager: DeviceManager, deviceFactory: VirtualDeviceFactory, settingsManager: SettingsManager, - logger: Logger + logger: Logger, ) { this.deviceManager = deviceManager; this.deviceFactory = deviceFactory; diff --git a/src/device/protocol/zc95/zc95Device.ts b/src/device/protocol/zc95/zc95Device.ts index 6336d9a0..68aa1d1e 100644 --- a/src/device/protocol/zc95/zc95Device.ts +++ b/src/device/protocol/zc95/zc95Device.ts @@ -7,7 +7,7 @@ import Zc95MessageFactory, { PowerStatusMsgResponse, } from './zc95MessageFactory.js'; import IntRangeDeviceAttribute, { - InitializedIntRangeDeviceAttribute + InitializedIntRangeDeviceAttribute, } from '../../attribute/intRangeDeviceAttribute.js'; import ListDeviceAttribute, { InitializedListDeviceAttribute } from '../../attribute/listDeviceAttribute.js'; import { InitializedBoolDeviceAttribute } from '../../attribute/boolDeviceAttribute.js'; @@ -28,22 +28,21 @@ import { DeviceId } from '../../deviceId.js'; type RequiredZc95DeviceAttributes = { activePattern: InitializedListDeviceAttribute; patternStarted: InitializedBoolDeviceAttribute; -} +}; type Zc95DevicePowerChannelAttributesKeyPrefix = `powerChannel`; type Zc95DevicePowerChannelAttributesKey = `${Zc95DevicePowerChannelAttributesKeyPrefix}${1 | 2 | 3 | 4}`; -type Zc95DevicePowerChannelAttributes = Record +type Zc95DevicePowerChannelAttributes = Record; type Zc95DevicePatternAttributesKeyPrefix = `patternAttribute`; type Zc95DevicePatternAttributesKey = `${Zc95DevicePatternAttributesKeyPrefix}${number}`; -type Zc95DevicePatternAttributes = Record> +type Zc95DevicePatternAttributes = Record>; export type Zc95DeviceAttributes = Partial & Zc95DevicePatternAttributes> & Required; - type AnyZc95DeviceAttribute = DeviceAttributeOf; type AttributeValue = AttributeValueOf; @@ -76,7 +75,7 @@ export default class Zc95Device extends PeripheralDevice, eventEmitter: EventEmitter, - logger: Logger + logger: Logger, ) { super(deviceId, deviceName, provider, connectedSince, controllable, protocol, transport, attributes, config, eventEmitter, logger); this.fwVersion = fwVersion; @@ -87,12 +86,12 @@ export default class Zc95Device extends PeripheralDevice + K extends AttributeKeyOf, >(attributeName: K, value: AttributeValue): Promise> { const attribute = this.attributes[attributeName]; if (!this.isAttributePresent(attribute)) { - throw new Error(`Attribute with name '${attributeName}' does not exist for this device`) + throw new Error(`Attribute with name '${attributeName}' does not exist for this device`); } if (this.isActivePatternAttribute(attribute) && attribute.isValidValue(value)) { @@ -120,12 +119,11 @@ export default class Zc95Device extends PeripheralDevice, value: number): Promise { - const menuItemId = parseInt(patternDetailAttr.name.slice(Zc95Device.patternAttributePrefix.length), 10); if (isNaN(menuItemId)) { @@ -142,14 +140,14 @@ export default class Zc95Device extends PeripheralDevice, - value: number + value: number, ): Promise { if (!this.allPowerChannelValuesDefined(this.attributes)) { throw new Error('Cannot set channel power before all channel values have been initialized'); @@ -179,10 +177,11 @@ export default class Zc95Device extends PeripheralDevice): attrs is { [K in keyof Zc95DevicePowerChannelAttributes]-?: InitializedIntRangeDeviceAttribute } { - return attrs.powerChannel1?.value !== undefined && - attrs.powerChannel2?.value !== undefined && - attrs.powerChannel3?.value !== undefined && - attrs.powerChannel4?.value !== undefined; + return attrs.powerChannel1?.value !== undefined + && attrs.powerChannel2?.value !== undefined + && attrs.powerChannel3?.value !== undefined + && attrs.powerChannel4?.value !== undefined + ; } private async setAttributeActivePattern(value: number): Promise { @@ -204,7 +203,7 @@ export default class Zc95Device extends PeripheralDevice { if ( - key.startsWith(Zc95Device.patternAttributePrefix) || - key.startsWith(Zc95Device.powerChannelAttributePrefix) + key.startsWith(Zc95Device.patternAttributePrefix) + || key.startsWith(Zc95Device.powerChannelAttributePrefix) ) { Reflect.deleteProperty(this.attributes, key); } @@ -269,13 +268,13 @@ export default class Zc95Device extends PeripheralDevice percentagePowerLimit + if (undefined !== this.attributes[channelAttrName]?.value + && this.attributes[channelAttrName].value > percentagePowerLimit ) { this.attributes[channelAttrName].value = percentagePowerLimit; } - channelAttr.value = Int.from(Math.floor(channel.MaxOutputPower * 0.1)) // or channel.OutputPower? + channelAttr.value = Int.from(Math.floor(channel.MaxOutputPower * 0.1)); // or channel.OutputPower? channelAttr.max = percentagePowerLimit; } @@ -297,7 +296,7 @@ export default class Zc95Device extends PeripheralDevice { return attribute !== undefined && attribute.name.startsWith(Zc95Device.powerChannelAttributePrefix) diff --git a/src/device/protocol/zc95/zc95DeviceFactory.ts b/src/device/protocol/zc95/zc95DeviceFactory.ts index bda9e140..fc9ec008 100644 --- a/src/device/protocol/zc95/zc95DeviceFactory.ts +++ b/src/device/protocol/zc95/zc95DeviceFactory.ts @@ -28,7 +28,7 @@ export default class Zc95DeviceFactory dateFactory: DateFactory, eventEmitterFactory: EventEmitterFactory, knownDeviceRegistry: KnownDeviceRegistry, - logger: Logger + logger: Logger, ) { this.dateFactory = dateFactory; this.eventEmitterFactory = eventEmitterFactory; @@ -43,16 +43,16 @@ export default class Zc95DeviceFactory transport: DeviceBidirectionalTransport, messageFactory: Zc95MessageFactory, messageResponseHandler: MessageResponseHandler, - provider: string + provider: string, ): Promise { try { const availablePatterns = (await messageResponseHandler.send( messageFactory.createGetPatterns(), - 2000 + 2000, )).Patterns; const attributes = this.getAttributes( - availablePatterns.map((pattern) => ({ key: Int.from(pattern.Id), value: pattern.Name })) + availablePatterns.map(pattern => ({ key: Int.from(pattern.Id), value: pattern.Name })), ); // We only receive serial no. info for ZC95 devices with fw >=2.0 @@ -95,11 +95,11 @@ export default class Zc95DeviceFactory private getAttributes(patterns: ListDeviceAttributeOptions): Zc95DeviceAttributes { const activePatternAttr = ListDeviceAttribute.createInitialized( - 'activePattern', 'Pattern', DeviceAttributeModifier.readWrite, patterns, Int.ZERO + 'activePattern', 'Pattern', DeviceAttributeModifier.readWrite, patterns, Int.ZERO, ); const patternStartedAttr = BoolDeviceAttribute.createInitialized( - 'patternStarted', 'Pattern Started', DeviceAttributeModifier.readWrite, false + 'patternStarted', 'Pattern Started', DeviceAttributeModifier.readWrite, false, ); return { diff --git a/src/device/protocol/zc95/zc95MessageFactory.ts b/src/device/protocol/zc95/zc95MessageFactory.ts index e16490e6..63dad057 100644 --- a/src/device/protocol/zc95/zc95MessageFactory.ts +++ b/src/device/protocol/zc95/zc95MessageFactory.ts @@ -3,33 +3,33 @@ import Zc95Protocol, { Msg, MsgAndResponseIdentifier, MsgResponse } from './zc95 export type GetPatternDetailMsg = { Type: 'GetPatternDetail'; Id: string; -} & Msg +} & Msg; export type GetPatternsMsg = { Type: 'GetPatterns'; -} & Msg +} & Msg; export type PatternStartMsg = { Type: 'PatternStart'; Index: number; -} & Msg +} & Msg; export type PatternMinMaxChangeMsg = { Type: 'PatternMinMaxChange'; MenuId: number; NewValue: number; -} & Msg +} & Msg; export type PatternMultiChoiceChangeMsg = { Type: 'PatternMultiChoiceChange'; MenuId: number; ChoiceId: number; -} & Msg +} & Msg; export type PatternSoftButtonMsg = { Type: 'PatternSoftButton'; Pressed: 0 | 1; -} & Msg +} & Msg; export type SetPowerMsg = { Type: 'SetPower'; @@ -37,73 +37,73 @@ export type SetPowerMsg = { Chan2: number; Chan3: number; Chan4: number; -} & Msg +} & Msg; export type PatternStopMsg = { Type: 'PatternStop'; -} & Msg +} & Msg; export type GetVersionMsg = { Type: 'GetVersion'; -} & Msg +} & Msg; export type LuaStartMsg = { Type: 'LuaStart'; Index: number; -} & Msg +} & Msg; export type LuaEndMsg = { Type: 'LuaEnd'; -} & Msg +} & Msg; export type LuaLineMsg = { Type: 'LuaLine'; LineNumber: number; Text: string; -} & Msg +} & Msg; export type GetLuaScriptsMsg = { Type: 'GetLuaScripts'; -} & Msg +} & Msg; export type DeleteLuaScriptMsg = { Type: 'DeleteLuaScript'; Index: number; -} & Msg +} & Msg; export type AckMsgResponse = { - Type: 'Ack', -} & MsgResponse + Type: 'Ack'; +} & MsgResponse; export type VersionMsgResponse = { - Type: 'VersionDetails', + Type: 'VersionDetails'; ZC95: string; WsMajor: number; WsMinor: number; SerialNo?: string; -} & MsgResponse +} & MsgResponse; type PatternDetail = { - Type: 'PatternDetail', + Type: 'PatternDetail'; Id: number; Name: string; -} +}; export type PatternsMsgResponse = { - Type: 'PatternList', + Type: 'PatternList'; Patterns: PatternDetail[]; -} & MsgResponse +} & MsgResponse; type ChannelPowerStatus = { Channel: 1 | 2 | 3 | 4; OutputPower: number; MaxOutputPower: number; PowerLimit: number; -} +}; export type PowerStatusMsgResponse = { - Channels: ChannelPowerStatus[] -} & MsgResponse + Channels: ChannelPowerStatus[]; +} & MsgResponse; export type MenuItem = { Id: number; @@ -111,37 +111,37 @@ export type MenuItem = { Group: number; Type: 'MIN_MAX' | 'MULTI_CHOICE'; Default: number; -} +}; export type MinMaxMenuItem = { Min: number; Max: number; IncrementStep: number; UoM: string; -} & MenuItem +} & MenuItem; export type MultiChoiceMenuItem = { - Choices: { Id: number; Name: string }[]; -} & MenuItem + Choices: { Id: number, Name: string }[]; +} & MenuItem; export type PatternDetailsMsgResponse = { - Type: 'PatternDetail', + Type: 'PatternDetail'; Name: string; Id: number; ButtonA: string; MenuItems: (MinMaxMenuItem | MultiChoiceMenuItem)[]; -} & MsgResponse +} & MsgResponse; type LuaScriptInfo = { Index: number; Empty: boolean; Valid: boolean; Name: string; -} +}; export type GetLuaScriptsMsgResponse = { Scripts: LuaScriptInfo[]; -} & MsgResponse +} & MsgResponse; export default class Zc95MessageFactory { @@ -154,7 +154,7 @@ export default class Zc95MessageFactory Type: 'GetPatterns', MsgId: msgId, }, - 'PatternList' + 'PatternList', ); } @@ -164,7 +164,7 @@ export default class Zc95MessageFactory { Type: 'GetPatternDetail', MsgId: msgId, - Id: String(patternId) + Id: String(patternId), }, 'PatternDetail', ); @@ -176,14 +176,14 @@ export default class Zc95MessageFactory { Type: 'PatternStart', MsgId: msgId, - Index: patternId + Index: patternId, }, 'Ack', ); } public createPatternMinMaxChange( - menuId: number, newValue: number + menuId: number, newValue: number, ): MsgAndResponseIdentifier { const msgId = this.getNextMsgIndex(); return Zc95Protocol.createMessage( @@ -191,14 +191,14 @@ export default class Zc95MessageFactory Type: 'PatternMinMaxChange', MsgId: msgId, MenuId: menuId, - NewValue: newValue + NewValue: newValue, }, - 'Ack' + 'Ack', ); } public createPatternMultiChoiceChange( - menuId: number, choiceId: number + menuId: number, choiceId: number, ): MsgAndResponseIdentifier { const msgId = this.getNextMsgIndex(); return Zc95Protocol.createMessage( @@ -208,7 +208,7 @@ export default class Zc95MessageFactory MenuId: menuId, ChoiceId: choiceId, }, - 'Ack' + 'Ack', ); } @@ -218,14 +218,14 @@ export default class Zc95MessageFactory { Type: 'PatternSoftButton', MsgId: msgId, - Pressed: pressed ? 1 : 0 + Pressed: pressed ? 1 : 0, }, - 'Ack' + 'Ack', ); } public createSetPower( - chan1: number, chan2: number, chan3: number, chan4: number + chan1: number, chan2: number, chan3: number, chan4: number, ): MsgAndResponseIdentifier { const msgId = this.getNextMsgIndex(); return Zc95Protocol.createMessage( @@ -235,9 +235,9 @@ export default class Zc95MessageFactory Chan1: chan1, Chan2: chan2, Chan3: chan3, - Chan4: chan4 + Chan4: chan4, }, - 'Ack' + 'Ack', ); } @@ -248,7 +248,7 @@ export default class Zc95MessageFactory Type: 'PatternStop', MsgId: msgId, }, - 'Ack' + 'Ack', ); } @@ -259,7 +259,7 @@ export default class Zc95MessageFactory Type: 'GetVersion', MsgId: msgId, }, - 'VersionDetails' + 'VersionDetails', ); } @@ -269,9 +269,9 @@ export default class Zc95MessageFactory { Type: 'LuaStart', MsgId: msgId, - Index: index + Index: index, }, - 'Ack' + 'Ack', ); } @@ -282,9 +282,9 @@ export default class Zc95MessageFactory Type: 'LuaLine', MsgId: msgId, LineNumber: lineNumber, - Text: text.trimEnd() + Text: text.trimEnd(), }, - 'Ack' + 'Ack', ); } @@ -295,7 +295,7 @@ export default class Zc95MessageFactory Type: 'LuaEnd', MsgId: msgId, }, - 'Ack' + 'Ack', ); } @@ -306,7 +306,7 @@ export default class Zc95MessageFactory Type: 'GetLuaScripts', MsgId: msgId, }, - 'LuaScripts' + 'LuaScripts', ); } @@ -316,9 +316,9 @@ export default class Zc95MessageFactory { Type: 'DeleteLuaScript', MsgId: msgId, - Index: index + Index: index, }, - 'Ack' + 'Ack', ); } diff --git a/src/device/protocol/zc95/zc95Protocol.ts b/src/device/protocol/zc95/zc95Protocol.ts index 3b1865be..62d0b188 100644 --- a/src/device/protocol/zc95/zc95Protocol.ts +++ b/src/device/protocol/zc95/zc95Protocol.ts @@ -6,23 +6,23 @@ type ResponseToKey = R extends { Type: infer T } ? T : ne export type Msg = { Type: string; MsgId: number; -} +}; export type MsgResponse = { Type: string; MsgId: number; - Result: 'OK' | 'ERROR' + Result: 'OK' | 'ERROR'; Error?: string; -} +}; export type ResponseIdentifier = { - msgId: number, - type: ResponseToKey -} + msgId: number; + type: ResponseToKey; +}; export type MsgAndResponseIdentifier = { responseIdentifier: ResponseIdentifier; -} & MessageWithResponse +} & MessageWithResponse; export type Zc95ProtocolMessage = MsgAndResponseIdentifier; @@ -42,15 +42,15 @@ export default class Zc95Protocol implements DeviceProtocol return { message: jsonResponse, - } + }; } catch (e: unknown) { const error = BaseError.normalize(e); return { error: { type: 'invalid_frame', reason: `Could not parse JSON: ${error.message}`, - } - } + }, + }; } } @@ -61,7 +61,7 @@ export default class Zc95Protocol implements DeviceProtocol public static createMessage( message: M, - responseType: ResponseToKey + responseType: ResponseToKey, ): MsgAndResponseIdentifier { const responseIdentifier: ResponseIdentifier = { msgId: message.MsgId, @@ -74,4 +74,4 @@ export default class Zc95Protocol implements DeviceProtocol responseIdentifier, }; } -} \ No newline at end of file +} diff --git a/src/device/protocol/zc95/zc95SerialDeviceProvider.ts b/src/device/protocol/zc95/zc95SerialDeviceProvider.ts index e290feaa..676cbd26 100644 --- a/src/device/protocol/zc95/zc95SerialDeviceProvider.ts +++ b/src/device/protocol/zc95/zc95SerialDeviceProvider.ts @@ -28,7 +28,7 @@ export default class Zc95SerialDeviceProvider extends SerialDeviceProvider): Promise { - const serialLogger = this.logger.child({ name: Zc95Device.name }) + const serialLogger = this.logger.child({ name: Zc95Device.name }); const parser = port.pipe(new FrameParser({ stx: Zc95Protocol.STX, etx: Zc95Protocol.ETX })); const serialPort = new SynchronousSerialPort(deviceDetectionInfo.portInfo, parser, port, serialLogger); const transport = this.transportFactory.create( - serialPort, Buffer.from([Zc95Protocol.STX]), Buffer.from([Zc95Protocol.ETX]) + serialPort, Buffer.from([Zc95Protocol.STX]), Buffer.from([Zc95Protocol.ETX]), ); const protocol = new Zc95Protocol(); const messageFactory = new Zc95MessageFactory(); @@ -66,7 +66,7 @@ export default class Zc95SerialDeviceProvider extends SerialDeviceProvider extends await promiseWithTimeout( peripheral.disconnectAsync(), 2000, - `Timed out while disconnecting from device ${peripheral.id}` + `Timed out while disconnecting from device ${peripheral.id}`, ); } catch (e: unknown) { logError(this.logger, `Error disconnecting peripheral ${peripheral.id}`, e); diff --git a/src/device/provider/deviceProvider.ts b/src/device/provider/deviceProvider.ts index dfed7c65..3b39aa3a 100644 --- a/src/device/provider/deviceProvider.ts +++ b/src/device/provider/deviceProvider.ts @@ -27,7 +27,7 @@ export default abstract class DeviceProvider logError(this.logger, 'Error in device detection handler', err) + (err: unknown) => logError(this.logger, 'Error in device detection handler', err), ); } @@ -130,7 +130,7 @@ export default abstract class DeviceProvider { + device.on(DeviceEvent.deviceDisconnected, d => { this.connectedDevices.delete(d.getDeviceId); this.logger.info(`Connected devices: ${this.connectedDevices.size}`); }); diff --git a/src/device/provider/deviceProviderFactory.ts b/src/device/provider/deviceProviderFactory.ts index 27808bef..cdcf25fa 100644 --- a/src/device/provider/deviceProviderFactory.ts +++ b/src/device/provider/deviceProviderFactory.ts @@ -3,5 +3,6 @@ import { JsonObject } from '../../types.js'; type DeviceProviderFactory = { create(config: JsonObject): DP; -} -export default DeviceProviderFactory +}; + +export default DeviceProviderFactory; diff --git a/src/device/provider/deviceProviderManager.ts b/src/device/provider/deviceProviderManager.ts index 999ee71f..77a28c24 100644 --- a/src/device/provider/deviceProviderManager.ts +++ b/src/device/provider/deviceProviderManager.ts @@ -9,7 +9,7 @@ import { logError } from '../../util/error.js'; type RunningProvider = { provider: AnyDeviceProvider; sourceFingerprint: string; -} +}; export default class DeviceProviderManager { @@ -25,7 +25,7 @@ export default class DeviceProviderManager public constructor( factories: Map>, - logger: Logger + logger: Logger, ) { this.factories = factories; this.logger = logger.child({ name: DeviceProviderManager.name }); @@ -126,7 +126,7 @@ export default class DeviceProviderManager const errors = results .filter((result): result is PromiseRejectedResult => 'rejected' === result.status) - .map((result) => result.reason); + .map(result => result.reason); for (const error of errors) { this.logger.error('Failed to stop device provider', error); diff --git a/src/device/provider/serialDeviceProvider.ts b/src/device/provider/serialDeviceProvider.ts index f6582380..312a46d4 100644 --- a/src/device/provider/serialDeviceProvider.ts +++ b/src/device/provider/serialDeviceProvider.ts @@ -23,7 +23,7 @@ export default abstract class SerialDeviceProvider; config: Record; -} +}; export type SerializedSlvCtrlPlusDevice = SerializedDeviceBase & { type: 'slvCtrlPlus'; deviceModel: string; fwVersion: number; protocolVersion: number; -} +}; export type SerializedButtplugIoDevice = SerializedDeviceBase & { type: 'buttplugIo'; deviceModel: string; -} +}; export type SerializedVirtualDevice = SerializedDeviceBase & { type: 'virtual'; deviceModel: string; fwVersion: string; -} +}; export type SerializedZc95Device = SerializedDeviceBase & { type: 'zc95'; fwVersion: string; -} +}; export type SerializedEStim2bDevice = SerializedDeviceBase & { type: 'estim2b'; fwVersion: string; -} +}; export type SerializedDevice = | SerializedSlvCtrlPlusDevice diff --git a/src/device/transport/bleDeviceTransport.ts b/src/device/transport/bleDeviceTransport.ts index 3aee9634..f2246c9f 100644 --- a/src/device/transport/bleDeviceTransport.ts +++ b/src/device/transport/bleDeviceTransport.ts @@ -24,7 +24,7 @@ export default class BleUartDeviceTransport implements DeviceBidirectionalTransp public static async create( peripheral: Peripheral, uartRxCharacteristicUuid: string, - uartTxCharacteristicUuid: string + uartTxCharacteristicUuid: string, ): Promise { const transport = new this(peripheral, uartRxCharacteristicUuid, uartTxCharacteristicUuid); await transport.subscribe(); @@ -36,8 +36,16 @@ export default class BleUartDeviceTransport implements DeviceBidirectionalTransp this.uartRxCharacteristicUuid = uartRxCharacteristicUuid; this.uartTxCharacteristicUuid = uartTxCharacteristicUuid; - this.connectHandler = asyncHandler(async (err: Error) => { if (null !== err) { return; } await this.subscribe() }, console.error); - this.disconnectHandler = (): void => { this.isConnected = false; }; + this.connectHandler = asyncHandler(async (err: Error) => { + if (null !== err) { + return; + } + await this.subscribe(); + }, console.error); + + this.disconnectHandler = (): void => { + this.isConnected = false; + }; this.peripheral.on('connect', this.connectHandler); this.peripheral.on('disconnect', this.disconnectHandler); @@ -64,8 +72,8 @@ export default class BleUartDeviceTransport implements DeviceBidirectionalTransp [this.uartRxCharacteristicUuid, this.uartTxCharacteristicUuid], ); - const rx = characteristics.find((c) => c.uuid === this.uartRxCharacteristicUuid); - const tx = characteristics.find((c) => c.uuid === this.uartTxCharacteristicUuid); + const rx = characteristics.find(c => c.uuid === this.uartRxCharacteristicUuid); + const tx = characteristics.find(c => c.uuid === this.uartTxCharacteristicUuid); if (!rx || !tx) { throw new Error('Missing UART RX/TX characteristics on device.'); @@ -161,4 +169,4 @@ export default class BleUartDeviceTransport implements DeviceBidirectionalTransp public getDeviceIdentifier(): string { return this.peripheral.id; } -} \ No newline at end of file +} diff --git a/src/device/transport/bleObserver.ts b/src/device/transport/bleObserver.ts index 9d5a4ab7..62dfe99c 100644 --- a/src/device/transport/bleObserver.ts +++ b/src/device/transport/bleObserver.ts @@ -29,7 +29,7 @@ export default class BleObserver extends SharedObserver public constructor( deviceManager: DeviceManager, - logger: Logger + logger: Logger, ) { super(logger.child({ name: BleObserver.name })); this.deviceManager = deviceManager; @@ -40,7 +40,9 @@ export default class BleObserver extends SharedObserver this.stopRequested = false; noble.on('discover', this.onDiscover.bind(this)); - noble.on('scanStop', () => { this.logger.info('Noble scanning stopped'); }); + noble.on('scanStop', () => { + this.logger.info('Noble scanning stopped'); + }); // Waiting for the adapter to power on can take an arbitrarily long time (or never happen // at all, e.g. no BLE hardware present), so it must not block start()/stop() @@ -114,7 +116,7 @@ export default class BleObserver extends SharedObserver private async waitForPoweredOnUnlessStopped(): Promise { let stopped = false; - const stopSignal = new Promise((resolve) => { + const stopSignal = new Promise(resolve => { this.cancelPowerOnWait = (): void => { stopped = true; resolve(); diff --git a/src/device/transport/deviceBidirectionalTransport.ts b/src/device/transport/deviceBidirectionalTransport.ts index 6ae36724..0d6d1c86 100644 --- a/src/device/transport/deviceBidirectionalTransport.ts +++ b/src/device/transport/deviceBidirectionalTransport.ts @@ -8,5 +8,6 @@ type DeviceTransport = { * @param timeout The timeout the transport should wait for the operation to complete and get a response */ sendAndAwaitReceive(data: Buffer, timeout?: number): Promise; -} & DeviceReadableTransport & DeviceWritableTransport -export default DeviceTransport +} & DeviceReadableTransport & DeviceWritableTransport; + +export default DeviceTransport; diff --git a/src/device/transport/deviceReadableTransport.ts b/src/device/transport/deviceReadableTransport.ts index afe2c0ed..462aff9f 100644 --- a/src/device/transport/deviceReadableTransport.ts +++ b/src/device/transport/deviceReadableTransport.ts @@ -25,5 +25,6 @@ type DeviceReadableTransport = { * Returns a deterministic and unique device identifier (for example hardware serial number) */ getDeviceIdentifier(): string; -} -export default DeviceReadableTransport +}; + +export default DeviceReadableTransport; diff --git a/src/device/transport/deviceWritableTransport.ts b/src/device/transport/deviceWritableTransport.ts index abfc8157..4eb09294 100644 --- a/src/device/transport/deviceWritableTransport.ts +++ b/src/device/transport/deviceWritableTransport.ts @@ -25,5 +25,6 @@ type DeviceWritableTransport = { * Returns a deterministic and unique device identifier (for example hardware serial number) */ getDeviceIdentifier(): string; -} -export default DeviceWritableTransport +}; + +export default DeviceWritableTransport; diff --git a/src/device/transport/serialPortObserver.ts b/src/device/transport/serialPortObserver.ts index 642aab8d..3c26d9be 100644 --- a/src/device/transport/serialPortObserver.ts +++ b/src/device/transport/serialPortObserver.ts @@ -28,7 +28,7 @@ export default class SerialPortObserver extends SharedObserver public constructor( deviceManager: DeviceManager, - logger: Logger + logger: Logger, ) { super(logger.child({ name: SerialPortObserver.name })); this.deviceManager = deviceManager; @@ -48,7 +48,7 @@ export default class SerialPortObserver extends SharedObserver this.rescanTimer = setTimeout(() => { this.rescanTimer = undefined; - this.discoveryQueue.run((cancellationToken) => this.discoverSerialDevices(cancellationToken)) + this.discoveryQueue.run(cancellationToken => this.discoverSerialDevices(cancellationToken)) .catch((e: unknown) => { if (e === cancellationTokenReasons.cancel) { this.logger.debug('Serial device discovery run cancelled (superseded by a later run or stopped)'); @@ -112,7 +112,7 @@ export default class SerialPortObserver extends SharedObserver const deviceInfo: SerialDeviceDetectionInfo = { type: 'serial', detectionId: DetectionId.create(portInfo.serialNumber), - portInfo + portInfo, }; this.managedDevices.set(portInfo.serialNumber, deviceInfo); diff --git a/src/device/updater/bufferedDeviceUpdater.ts b/src/device/updater/bufferedDeviceUpdater.ts index 3ce936e7..b219b2f3 100644 --- a/src/device/updater/bufferedDeviceUpdater.ts +++ b/src/device/updater/bufferedDeviceUpdater.ts @@ -11,7 +11,7 @@ export default class BufferedDeviceUpdater implements DeviceUpdaterInterface private static readonly handleUpdate = async ( deviceUpdater: DeviceUpdaterInterface, device: AnyDevice, - deviceData: DeviceData + deviceData: DeviceData, ): Promise => { await deviceUpdater.update(device, deviceData); }; diff --git a/src/device/updater/deviceUpdaterInterface.ts b/src/device/updater/deviceUpdaterInterface.ts index 4719f482..bc0617db 100644 --- a/src/device/updater/deviceUpdaterInterface.ts +++ b/src/device/updater/deviceUpdaterInterface.ts @@ -2,5 +2,6 @@ import { AnyDevice, DeviceData } from '../device.js'; type DeviceUpdaterInterface = { update(device: AnyDevice, rawData: DeviceData): Promise; -} -export default DeviceUpdaterInterface +}; + +export default DeviceUpdaterInterface; diff --git a/src/factory/uuidFactory.ts b/src/factory/uuidFactory.ts index 9ae6e87f..a0d1526f 100644 --- a/src/factory/uuidFactory.ts +++ b/src/factory/uuidFactory.ts @@ -2,7 +2,7 @@ import { validate as isUuid, v4 as uuidv4, v5 as uuidv5 } from 'uuid'; export default class UuidFactory { - public static readonly DEFAULT_NAMESPACE = '1eb996d5-15ee-4c2c-9a4c-4f84a7dedb56' + public static readonly DEFAULT_NAMESPACE = '1eb996d5-15ee-4c2c-9a4c-4f84a7dedb56'; private readonly namespace: string; diff --git a/src/health/healthMetricsCollector.ts b/src/health/healthMetricsCollector.ts index aaa4236b..78f10d2f 100644 --- a/src/health/healthMetricsCollector.ts +++ b/src/health/healthMetricsCollector.ts @@ -41,7 +41,11 @@ export default class HealthMetricsCollector this.intervalHandle = setIntervalAsync( async () => await this.refresh(), - { intervalMs, timeoutMs: intervalMs * 3, onError: (err) => logError(this.logger, `Health metrics refresh failed`, err) }, + { + intervalMs, + timeoutMs: intervalMs * 3, + onError: err => logError(this.logger, `Health metrics refresh failed`, err), + }, ); } @@ -86,13 +90,15 @@ export default class HealthMetricsCollector cores: true === cpuInfo.success ? cpuInfo.data.cores : null, model: true === cpuInfo.success ? cpuInfo.data.model : null, }, - memory: true === memInfo.success ? { - totalMemMb: memInfo.data.total.toMB(), - usedMemMb: memInfo.data.used.toMB(), - freeMemMb: memInfo.data.available.toMB(), - usedMemPercentage: memInfo.data.usagePercentage, - freeMemPercentage: 100 - memInfo.data.usagePercentage, - } : null, + memory: true === memInfo.success + ? { + totalMemMb: memInfo.data.total.toMB(), + usedMemMb: memInfo.data.used.toMB(), + freeMemMb: memInfo.data.available.toMB(), + usedMemPercentage: memInfo.data.usagePercentage, + freeMemPercentage: 100 - memInfo.data.usagePercentage, + } + : null, os: { name: os.version(), type: os.type(), @@ -109,7 +115,7 @@ export default class HealthMetricsCollector : null, hostname: os.hostname(), uptime: true === sysUptime.success ? Math.floor(sysUptime.data.uptime / 1000) : null, - } + }, }; this.currentMetrics = metrics; diff --git a/src/health/serializedTypes.ts b/src/health/serializedTypes.ts index fe8628b8..b5ae6413 100644 --- a/src/health/serializedTypes.ts +++ b/src/health/serializedTypes.ts @@ -2,33 +2,33 @@ import { NetworkStats } from 'node-os-utils'; export type SerializedHealthMetrics = { process: { - memoryUsage: NodeJS.MemoryUsage, - }, + memoryUsage: NodeJS.MemoryUsage; + }; system: { cpu: { - usage: number | null, - average: number | null, - cores: number | null, - model: string | null, - }, + usage: number | null; + average: number | null; + cores: number | null; + model: string | null; + }; memory: { totalMemMb: number; usedMemMb: number; freeMemMb: number; usedMemPercentage: number; freeMemPercentage: number; - } | null, + } | null; os: { - name: string, - type: string, - arch: string, - platform: string, - }, + name: string; + type: string; + arch: string; + platform: string; + }; network: { - netstat: NetworkStats[] | null, - }, - ip?: string | null, - hostname: string | null, - uptime: number | null, - }, -} + netstat: NetworkStats[] | null; + }; + ip?: string | null; + hostname: string | null; + uptime: number | null; + }; +}; diff --git a/src/logging/Logger.ts b/src/logging/Logger.ts index 498aa313..f2284b96 100644 --- a/src/logging/Logger.ts +++ b/src/logging/Logger.ts @@ -1,10 +1,5 @@ -export type ChildLoggerBindings = { - name?: string, -} - -export type ChildLoggerOptions = { - level?: string, -} +export type ChildLoggerBindings = { name?: string }; +export type ChildLoggerOptions = { level?: string }; export type ErrorContext = Error | ({ error: Error } & Record); @@ -17,5 +12,6 @@ type Logger = { warn(msg: string, context?: unknown): void; error(msg: string, context?: ErrorContext | unknown): void; fatal(msg: string, context?: unknown): void; -} -export default Logger +}; + +export default Logger; diff --git a/src/logging/PinoLogger.ts b/src/logging/PinoLogger.ts index ce0348a0..5caf06b3 100644 --- a/src/logging/PinoLogger.ts +++ b/src/logging/PinoLogger.ts @@ -12,7 +12,7 @@ export default class PinoLogger implements Logger public child(bindings?: ChildLoggerBindings, options?: ChildLoggerOptions): Logger { return new PinoLogger(this.pino.child( bindings ?? {}, - undefined === options ? undefined : options + undefined === options ? undefined : options, )); } diff --git a/src/middleware/contentTypeMiddleware.ts b/src/middleware/contentTypeMiddleware.ts index 2c129843..26d02468 100644 --- a/src/middleware/contentTypeMiddleware.ts +++ b/src/middleware/contentTypeMiddleware.ts @@ -12,7 +12,7 @@ export default (req: Request, res: Response, next: NextFunction): void => { if (!req.accepts(supportedContentType)) { res.status(406).send('Accept header must be application/json'); return; - }*/ + } */ next(); -} +}; diff --git a/src/repository/automationScriptRepository.ts b/src/repository/automationScriptRepository.ts index 8cff2c04..a0567a38 100644 --- a/src/repository/automationScriptRepository.ts +++ b/src/repository/automationScriptRepository.ts @@ -6,7 +6,6 @@ import fs from 'fs'; export default class AutomationScriptRepository implements AutomationScriptRepositoryInterface { - private readonly location: string; public constructor(location: string) @@ -26,7 +25,7 @@ export default class AutomationScriptRepository implements AutomationScriptRepos return scripts; } - public getByName(name: string): string|null + public getByName(name: string): string | null { try { return fs.readFileSync(this.resolveScriptPath(name), 'utf8'); diff --git a/src/repository/automationScriptRepositoryInterface.ts b/src/repository/automationScriptRepositoryInterface.ts index a2305250..f89bccbc 100644 --- a/src/repository/automationScriptRepositoryInterface.ts +++ b/src/repository/automationScriptRepositoryInterface.ts @@ -3,10 +3,11 @@ import AutomationScript from '../entity/automationScript.js'; type AutomationScriptRepositoryInterface = { getAll(): AutomationScript[]; - getByName(name: string): string|null; + getByName(name: string): string | null; save(fileName: string, data: string): void; delete(fileName: string): void; -} -export default AutomationScriptRepositoryInterface +}; + +export default AutomationScriptRepositoryInterface; diff --git a/src/repository/deviceRepositoryInterface.ts b/src/repository/deviceRepositoryInterface.ts index b30a36d4..5018aee0 100644 --- a/src/repository/deviceRepositoryInterface.ts +++ b/src/repository/deviceRepositoryInterface.ts @@ -3,6 +3,7 @@ import { AnyDevice } from '../device/device.js'; type DeviceRepositoryInterface = { getAll(): AnyDevice[]; - getById(uuid: string): AnyDevice|null; -} -export default DeviceRepositoryInterface + getById(uuid: string): AnyDevice | null; +}; + +export default DeviceRepositoryInterface; diff --git a/src/schemaValidation/JsonSchemaValidatorFactory.ts b/src/schemaValidation/JsonSchemaValidatorFactory.ts index 6424b717..2f17e907 100644 --- a/src/schemaValidation/JsonSchemaValidatorFactory.ts +++ b/src/schemaValidation/JsonSchemaValidatorFactory.ts @@ -5,7 +5,6 @@ import { TSchema } from '@sinclair/typebox'; export default class JsonSchemaValidatorFactory { - private readonly ajv: Ajv; public constructor(ajv: Ajv) { diff --git a/src/serial/frameParser.ts b/src/serial/frameParser.ts index 05d168ff..9ff924a0 100644 --- a/src/serial/frameParser.ts +++ b/src/serial/frameParser.ts @@ -4,7 +4,7 @@ export type FrameParserOptions = { stx: number; etx: number; maxMessageSize?: number; -} & TransformOptions +} & TransformOptions; type State = 'IDLE' | 'RECV'; diff --git a/src/serial/synchronousSerialPort.ts b/src/serial/synchronousSerialPort.ts index f6b066fd..d6e456f9 100644 --- a/src/serial/synchronousSerialPort.ts +++ b/src/serial/synchronousSerialPort.ts @@ -76,8 +76,8 @@ export default class SynchronousSerialPort this.reader.off('close', this.handleStreamClose); if (this.writer.isOpen) { - await new Promise((resolve) => { - this.writer.close((err) => { + await new Promise(resolve => { + this.writer.close(err => { if (err) { logError(this.logger, `Error while closing serial port '${this.portInfo.path}'`, err); } @@ -120,7 +120,7 @@ export default class SynchronousSerialPort removeListeners = (): void => { this.reader.removeListener('data', dataHandler); this.reader.removeListener('error', errorHandler); - } + }; this.reader.on('data', dataHandler); this.reader.on('error', errorHandler); diff --git a/src/serialization/discriminator/genericDeviceAttributeDiscriminator.ts b/src/serialization/discriminator/genericDeviceAttributeDiscriminator.ts index 1255e415..8fbcccee 100644 --- a/src/serialization/discriminator/genericDeviceAttributeDiscriminator.ts +++ b/src/serialization/discriminator/genericDeviceAttributeDiscriminator.ts @@ -12,7 +12,7 @@ const genericDeviceAttributeDiscriminator = new ObjectDiscriminator([ { value: FloatDeviceAttribute, name: 'float' }, { value: StrDeviceAttribute, name: 'str' }, { value: IntRangeDeviceAttribute, name: 'range' }, - { value: ListDeviceAttribute, name: 'list' } + { value: ListDeviceAttribute, name: 'list' }, ]); export default genericDeviceAttributeDiscriminator; diff --git a/src/serialization/discriminator/objectDiscriminator.ts b/src/serialization/discriminator/objectDiscriminator.ts index 83db43ef..9bc47969 100644 --- a/src/serialization/discriminator/objectDiscriminator.ts +++ b/src/serialization/discriminator/objectDiscriminator.ts @@ -38,7 +38,7 @@ export default class ObjectDiscriminator { return { discriminator: { property: typePropertyName, - subTypes: this.discriminatorMap + subTypes: this.discriminatorMap, }, }; } diff --git a/src/serialization/plainToClassSerializer.ts b/src/serialization/plainToClassSerializer.ts index 2189934e..f5e1b0b8 100644 --- a/src/serialization/plainToClassSerializer.ts +++ b/src/serialization/plainToClassSerializer.ts @@ -25,7 +25,7 @@ export default class PlainToClassSerializer if (!this.ajv.validate(schema, plain)) { throw new SchemaValidationError( this.ajv.errorsText(this.ajv.errors), - this.ajv.errors ?? [] + this.ajv.errors ?? [], ); } diff --git a/src/serviceMap.ts b/src/serviceMap.ts index 993fcdf5..9424d7a3 100644 --- a/src/serviceMap.ts +++ b/src/serviceMap.ts @@ -53,81 +53,80 @@ import AiroticDeviceProvider from './device/protocol/airotic/airoticDeviceProvid import AiroticDeviceFactory from './device/protocol/airotic/airoticDeviceFactory.js'; import KnownDeviceRegistry from './device/knownDeviceRegistry.js'; - type ServiceMap = { /* serializerServiceProvider */ - 'serializer.classToPlain': ClassToPlainSerializer, - 'serializer.plainToClass': PlainToClassSerializer, + 'serializer.classToPlain': ClassToPlainSerializer; + 'serializer.plainToClass': PlainToClassSerializer; /* loggerServiceProvider */ - 'logger.default': Logger, + 'logger.default': Logger; /* deviceServiceProvider */ - 'device.manager': DeviceManager, - 'device.serial.transport.factory': SerialDeviceTransportFactory, - 'device.provider.factory.virtual': VirtualDeviceProviderFactory, - 'device.serial.factory.slvCtrlPlus': SlvCtrlPlusDeviceFactory, - 'device.factory.zc95': Zc95DeviceFactory, - 'device.factory.estim2b': Estim2bDeviceFactory, - 'device.factory.airotic': AiroticDeviceFactory, - 'device.provider.factory.slvCtrlPlusSerial': DeviceProviderFactory, - 'device.provider.factory.zc95Serial': DeviceProviderFactory, - 'device.provider.factory.estim2bSerial': DeviceProviderFactory, - 'device.provider.factory.buttplugIoWebsocket': DeviceProviderFactory, - 'device.provider.factory.airotic': DeviceProviderFactory, - 'device.serial.factory.buttplugIo': ButtplugIoDeviceFactory, - 'device.virtual.provider': VirtualDeviceProvider, - 'device.virtual.factory': VirtualDeviceFactory, - 'device.uniqueNameGenerator': DeviceNameGenerator, - 'device.knownDeviceRegistry': KnownDeviceRegistry, - 'device.updater': DeviceUpdaterInterface, - 'device.observer.serial': SerialPortObserver, - 'device.observer.ble': BleObserver, + 'device.manager': DeviceManager; + 'device.serial.transport.factory': SerialDeviceTransportFactory; + 'device.provider.factory.virtual': VirtualDeviceProviderFactory; + 'device.serial.factory.slvCtrlPlus': SlvCtrlPlusDeviceFactory; + 'device.factory.zc95': Zc95DeviceFactory; + 'device.factory.estim2b': Estim2bDeviceFactory; + 'device.factory.airotic': AiroticDeviceFactory; + 'device.provider.factory.slvCtrlPlusSerial': DeviceProviderFactory; + 'device.provider.factory.zc95Serial': DeviceProviderFactory; + 'device.provider.factory.estim2bSerial': DeviceProviderFactory; + 'device.provider.factory.buttplugIoWebsocket': DeviceProviderFactory; + 'device.provider.factory.airotic': DeviceProviderFactory; + 'device.serial.factory.buttplugIo': ButtplugIoDeviceFactory; + 'device.virtual.provider': VirtualDeviceProvider; + 'device.virtual.factory': VirtualDeviceFactory; + 'device.uniqueNameGenerator': DeviceNameGenerator; + 'device.knownDeviceRegistry': KnownDeviceRegistry; + 'device.updater': DeviceUpdaterInterface; + 'device.observer.serial': SerialPortObserver; + 'device.observer.ble': BleObserver; /* factoryServiceProvider */ - 'factory.uuid': UuidFactory, - 'factory.date': DateFactory, - 'factory.serialPort': SerialPortFactory, - 'factory.eventEmitter': EventEmitterFactory, + 'factory.uuid': UuidFactory; + 'factory.date': DateFactory; + 'factory.serialPort': SerialPortFactory; + 'factory.eventEmitter': EventEmitterFactory; /* schemaValidationServiceProvider */ - 'ajv': Ajv, - 'factory.validator.schema.json': JsonSchemaValidatorFactory, + 'ajv': Ajv; + 'factory.validator.schema.json': JsonSchemaValidatorFactory; /* settingsServiceProvider */ - 'settings': Settings, - 'settings.manager': SettingsManager, + 'settings': Settings; + 'settings.manager': SettingsManager; /* automationServiceProvider */ - 'automation.scriptVmFactory': ScriptVmFactory, - 'automation.scriptRuntime': ScriptRuntime, + 'automation.scriptVmFactory': ScriptVmFactory; + 'automation.scriptRuntime': ScriptRuntime; /* repositoryServiceProvider */ - 'repository.connectedDevices': ConnectedDeviceRepository, - 'repository.automationScript': AutomationScriptRepository, + 'repository.connectedDevices': ConnectedDeviceRepository; + 'repository.automationScript': AutomationScriptRepository; - 'device.provider.manager': DeviceProviderManager, - 'socket.deviceUpdateHandler': DeviceUpdateHandler, + 'device.provider.manager': DeviceProviderManager; + 'socket.deviceUpdateHandler': DeviceUpdateHandler; /* controllerServiceProvider */ - 'controller.health': HealthController, - 'controller.getDevices': GetDevicesController, - 'controller.getDevice': GetDeviceController, - 'controller.patchDevice': PatchDeviceController, - 'controller.automation.getScripts': GetScriptsController, - 'controller.automation.getScript': GetScriptController, - 'controller.automation.createScript': CreateScriptController, - 'controller.automation.deleteScript': DeleteScriptController, - 'controller.automation.getLog': GetLogController, - 'controller.automation.runScript': RunScriptController, - 'controller.automation.stopScript': StopScriptController, - 'controller.automation.statusScript': StatusScriptController, - 'controller.settings.get': GetSettingsController, - 'controller.settings.put': PutSettingsController, - 'controller.version': VersionController, + 'controller.health': HealthController; + 'controller.getDevices': GetDevicesController; + 'controller.getDevice': GetDeviceController; + 'controller.patchDevice': PatchDeviceController; + 'controller.automation.getScripts': GetScriptsController; + 'controller.automation.getScript': GetScriptController; + 'controller.automation.createScript': CreateScriptController; + 'controller.automation.deleteScript': DeleteScriptController; + 'controller.automation.getLog': GetLogController; + 'controller.automation.runScript': RunScriptController; + 'controller.automation.stopScript': StopScriptController; + 'controller.automation.statusScript': StatusScriptController; + 'controller.settings.get': GetSettingsController; + 'controller.settings.put': PutSettingsController; + 'controller.version': VersionController; /* healthServiceProvider */ - 'health.metricsCollector': HealthMetricsCollector, -} + 'health.metricsCollector': HealthMetricsCollector; +}; export default ServiceMap; diff --git a/src/serviceProvider/deviceServiceProvider.ts b/src/serviceProvider/deviceServiceProvider.ts index 122baa81..4895a432 100644 --- a/src/serviceProvider/deviceServiceProvider.ts +++ b/src/serviceProvider/deviceServiceProvider.ts @@ -29,7 +29,7 @@ import PiperVirtualDeviceLogic from '../device/protocol/virtual/audio/piperVirtu import { piperVirtualDeviceConfigSchema } from '../device/protocol/virtual/audio/piperVirtualDeviceConfig.js'; import { noDeviceConfigSchema } from '../device/deviceConfig.js'; import { - randomGeneratorVirtualDeviceConfigSchema + randomGeneratorVirtualDeviceConfigSchema, } from '../device/protocol/virtual/randomGenerator/randomGeneratorVirtualDeviceConfig.js'; import { ttsVirtualDeviceConfigSchema } from '../device/protocol/virtual/audio/ttsVirtualDeviceConfig.js'; import GenericVirtualDeviceLogicFactory from '../device/protocol/virtual/genericVirtualDeviceLogicFactory.js'; @@ -46,7 +46,7 @@ export default class DeviceServiceProvider implements ServiceProvider): void { container.set( 'device.serial.transport.factory', - () => new SerialDeviceTransportFactory() + () => new SerialDeviceTransportFactory(), ); container.set( @@ -59,7 +59,7 @@ export default class DeviceServiceProvider implements ServiceProvider { return new DeviceManager( container.get('factory.eventEmitter').create(), container.get('settings.manager'), - container.get('logger.default') + container.get('logger.default'), ); }); @@ -84,11 +84,11 @@ export default class DeviceServiceProvider implements ServiceProvider new KnownDeviceRegistry( container.get('settings'), @@ -168,7 +168,7 @@ export default class DeviceServiceProvider implements ServiceProvider { const plainToClass = container.get('serializer.plainToClass'); @@ -247,14 +247,14 @@ export default class DeviceServiceProvider implements ServiceProvider { return new SerialPortObserver( container.get('device.manager'), - container.get('logger.default') + container.get('logger.default'), ); }); container.set('device.observer.ble', () => { return new BleObserver( container.get('device.manager'), - container.get('logger.default') + container.get('logger.default'), ); }); } diff --git a/src/serviceProvider/schemaValidationServiceProvider.ts b/src/serviceProvider/schemaValidationServiceProvider.ts index da446bf4..33fb1610 100644 --- a/src/serviceProvider/schemaValidationServiceProvider.ts +++ b/src/serviceProvider/schemaValidationServiceProvider.ts @@ -1,6 +1,6 @@ import { Pimple, ServiceProvider } from '@timesplinter/pimple'; import ServiceMap from '../serviceMap.js'; -import type { Ajv } from 'ajv' +import type { Ajv } from 'ajv'; import { Ajv2020 } from 'ajv/dist/2020.js'; import ajvFormatsPlugin from 'ajv-formats'; import JsonSchemaValidatorFactory from '../schemaValidation/JsonSchemaValidatorFactory.js'; @@ -16,9 +16,9 @@ export default class SchemaValidationServiceProvider implements ServiceProvider< }); container.set('factory.validator.schema.json', (): JsonSchemaValidatorFactory => { - return new JsonSchemaValidatorFactory( - container.get('ajv') - ); + return new JsonSchemaValidatorFactory( + container.get('ajv'), + ); }); } } diff --git a/src/serviceProvider/settingsServiceProvider.ts b/src/serviceProvider/settingsServiceProvider.ts index c4d16282..5d19437e 100644 --- a/src/serviceProvider/settingsServiceProvider.ts +++ b/src/serviceProvider/settingsServiceProvider.ts @@ -33,6 +33,6 @@ export default class SettingsServiceProvider implements ServiceProvider { return container.get('settings.manager').load(); - }) + }); } } diff --git a/src/settings/knownDevice.ts b/src/settings/knownDevice.ts index 73454c43..6aab3116 100644 --- a/src/settings/knownDevice.ts +++ b/src/settings/knownDevice.ts @@ -24,7 +24,7 @@ export default class KnownDevice private readonly _enabled: boolean; public constructor( - id: DeviceId, name: string, type: string, source: string, config: JsonObject = {}, enabled = true + id: DeviceId, name: string, type: string, source: string, config: JsonObject = {}, enabled = true, ) { this._id = id; this._name = name; diff --git a/src/settings/serializedTypes.ts b/src/settings/serializedTypes.ts index 03262aa1..c762548a 100644 --- a/src/settings/serializedTypes.ts +++ b/src/settings/serializedTypes.ts @@ -8,16 +8,16 @@ export type SerializedKnownDevice = { source: string; config: JsonObject; enabled: boolean; -} +}; export type SerializedDeviceSource = { id: string; type: string; config: JsonObject; enabled: boolean; -} +}; export type SerializedSettings = { knownDevices: Record; deviceSources: Record; -} +}; diff --git a/src/settings/settings.ts b/src/settings/settings.ts index 65c7b9ee..3316f3dd 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -6,38 +6,38 @@ import { DeviceId } from '../device/deviceId.js'; import { Type } from '@sinclair/typebox'; export const SettingsSchema = Type.Object({ - knownDevices: Type.Record( - Type.String(), - Type.Object({ - id: Type.String({ format: 'uuid' }), - serialNo: Type.Optional(Type.String()), - name: Type.String(), - type: Type.String(), - source: Type.String(), - config: Type.Optional(Type.Object({}, { additionalProperties: true })), - enabled: Type.Optional(Type.Boolean({ default: true })) - }, { - default: {}, - additionalProperties: false, - required: ['id', 'name', 'type', 'source'] - }) - ), - deviceSources: Type.Record( - Type.String(), - Type.Object({ - id: Type.String({ format: 'uuid' }), - type: Type.String(), - config: Type.Object({}, { additionalProperties: true }), - enabled: Type.Optional(Type.Boolean({ default: true })) - }, { - default: {}, - additionalProperties: false, - required: ['id', 'type', 'config'] - }) - ) + knownDevices: Type.Record( + Type.String(), + Type.Object({ + id: Type.String({ format: 'uuid' }), + serialNo: Type.Optional(Type.String()), + name: Type.String(), + type: Type.String(), + source: Type.String(), + config: Type.Optional(Type.Object({}, { additionalProperties: true })), + enabled: Type.Optional(Type.Boolean({ default: true })), + }, { + default: {}, + additionalProperties: false, + required: ['id', 'name', 'type', 'source'], + }), + ), + deviceSources: Type.Record( + Type.String(), + Type.Object({ + id: Type.String({ format: 'uuid' }), + type: Type.String(), + config: Type.Object({}, { additionalProperties: true }), + enabled: Type.Optional(Type.Boolean({ default: true })), + }, { + default: {}, + additionalProperties: false, + required: ['id', 'type', 'config'], + }), + ), }, { - additionalProperties: false, - required: ['knownDevices', 'deviceSources'] + additionalProperties: false, + required: ['knownDevices', 'deviceSources'], }); @Exclude() @@ -76,7 +76,7 @@ export default class Settings return filteredDevices; } - public getKnownDeviceById(id: DeviceId): KnownDevice|undefined + public getKnownDeviceById(id: DeviceId): KnownDevice | undefined { // Return already existing device if already known (previously detected serial number) return this.knownDevices.get(id); diff --git a/src/settings/settingsManager.ts b/src/settings/settingsManager.ts index 02cefc12..baee2ee3 100644 --- a/src/settings/settingsManager.ts +++ b/src/settings/settingsManager.ts @@ -12,10 +12,9 @@ import SettingsEventType from './settingsEventType.js'; import { JsonObject } from '../types.js'; import { logError } from '../util/error.js'; - type SettingsEvents = { - [SettingsEventType.changed]: (settings: Settings) => void, -} + [SettingsEventType.changed]: (settings: Settings) => void; +}; export default class SettingsManager { @@ -41,7 +40,7 @@ export default class SettingsManager plainToClassSerializer: PlainToClassSerializer, classToPlainSerializer: ClassToPlainSerializer, eventEmitter: EventEmitter, - logger: Logger + logger: Logger, ) { this.settingsFilePath = settingsFilePath; this.plainToClassSerializer = plainToClassSerializer; @@ -103,7 +102,7 @@ export default class SettingsManager this.watcher.on('error', (err: unknown) => logError( this.logger, `Settings file watcher error for '${this.settingsFilePath}'`, - err + err, )); this.logger.debug(`Watching '${this.settingsFilePath}' for external changes`); @@ -118,19 +117,19 @@ export default class SettingsManager this.watcher = undefined; } - public on (event: E, listener: SettingsEvents[E]): this + public on(event: E, listener: SettingsEvents[E]): this { this.eventEmitter.on(event, listener); return this; } - public off (event: E, listener: SettingsEvents[E]): this + public off(event: E, listener: SettingsEvents[E]): this { this.eventEmitter.off(event, listener); return this; } - public getSettings(): Settings|undefined { + public getSettings(): Settings | undefined { return this.settings; } @@ -207,9 +206,9 @@ export default class SettingsManager settings.addDeviceSource(new DeviceSource( 'b6a0f45e-c3d0-4dca-ab81-7daac0764291', SlvCtrlPlusSerialDeviceProvider.providerName, - {} + {}, )); return settings; } -} \ No newline at end of file +} diff --git a/src/socket/deviceUpdateHandler.ts b/src/socket/deviceUpdateHandler.ts index ba316983..26b238bc 100644 --- a/src/socket/deviceUpdateHandler.ts +++ b/src/socket/deviceUpdateHandler.ts @@ -15,7 +15,7 @@ export default class DeviceUpdateHandler public constructor( connectedDeviceRepository: ConnectedDeviceRepository, deviceUpdater: DeviceUpdaterInterface, - logger: Logger + logger: Logger, ) { this.connectedDeviceRepository = connectedDeviceRepository; this.deviceUpdater = deviceUpdater; diff --git a/src/socket/types.ts b/src/socket/types.ts index c70eaa53..d6ceda38 100644 --- a/src/socket/types.ts +++ b/src/socket/types.ts @@ -8,11 +8,11 @@ import { SerializedDevice } from '../device/serializedTypes.js'; import { SerializedSettings } from '../settings/serializedTypes.js'; import { DeviceId } from '../device/deviceId.js'; -export type DeviceUpdateData = { deviceId: DeviceId, data: DeviceData } +export type DeviceUpdateData = { deviceId: DeviceId, data: DeviceData }; export type ClientToServerEvents = { [WebSocketEvent.deviceUpdateReceived]: (data: DeviceUpdateData) => void; -} +}; export type ServerToClientEvents = { [WebSocketEvent.deviceConnected]: (data: SerializedDevice) => void; @@ -22,6 +22,6 @@ export type ServerToClientEvents = { [WebSocketEvent.healthMetrics]: (data: SerializedHealthMetrics) => void; [SettingsEventType.changed]: (data: SerializedSettings) => void; [AutomationEventType.consoleLog]: (data: string) => void; -} +}; export type WebsocketServer = Server; diff --git a/src/types.d.ts b/src/types.d.ts index 75a8d989..15fadf36 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,4 +1,4 @@ -export type JsonObject = { [key: string]: JsonValue } +export type JsonObject = { [key: string]: JsonValue }; export type JsonValue = | null diff --git a/src/util/async.ts b/src/util/async.ts index 50ac1a76..45bf7ed8 100644 --- a/src/util/async.ts +++ b/src/util/async.ts @@ -1,10 +1,10 @@ export const sleep = (ms: number): Promise => new Promise(r => setTimeout(r, ms)); export class IntervalTimeoutError extends Error { - public constructor(timeoutMs: number) { - super(`Interval function timed out (>${timeoutMs}ms)`); - this.name = 'IntervalTimeoutError'; - } + public constructor(timeoutMs: number) { + super(`Interval function timed out (>${timeoutMs}ms)`); + this.name = 'IntervalTimeoutError'; + } } export const setImmediateInterval = ( @@ -21,18 +21,18 @@ export type IntervalAsyncOptions = { runImmediately?: boolean; timeoutMs?: number; onError?: (err: unknown) => void; -} +}; export const asyncHandler = ( fn: (...args: TArgs) => Promise, - onError: (err: unknown) => void + onError: (err: unknown) => void, ): (...args: TArgs) => void => { return (...args: TArgs): void => { fn(...args).catch(onError); }; }; -export type IntervalAsync = { clear: () => void } +export type IntervalAsync = { clear: () => void }; export const setIntervalAsync = ( fn: (...args: TArgs) => Promise, @@ -52,7 +52,7 @@ export const setIntervalAsync = ( promises.push(new Promise((_, reject) => timeoutHandle = setTimeout(() => { reject(new IntervalTimeoutError(timeoutMs)); - }, timeoutMs)) + }, timeoutMs)), ); } @@ -85,14 +85,14 @@ export const setIntervalAsync = ( clear: (): void => { stopped = true; if (timer) clearTimeout(timer); - } + }, }; -} +}; export const promiseWithTimeout = ( promise: Promise, timeoutMs: number, - timeoutMessage = `Promise timed out after ${timeoutMs}ms` + timeoutMessage = `Promise timed out after ${timeoutMs}ms`, ): Promise => { let timeoutHandle: ReturnType; @@ -103,4 +103,4 @@ export const promiseWithTimeout = ( return Promise.race([promise, timeoutPromise]).finally(() => { clearTimeout(timeoutHandle); }); -}; \ No newline at end of file +}; diff --git a/src/util/dictionary.ts b/src/util/dictionary.ts index 6e92294f..2fe0a9e0 100644 --- a/src/util/dictionary.ts +++ b/src/util/dictionary.ts @@ -22,7 +22,7 @@ export const starWarsPlanets: string[] = [ 'Ryloth', 'Tatooine', 'Utapau', - 'Yavin' + 'Yavin', ]; export const starWarsSpecies: string[] = [ @@ -39,4 +39,4 @@ export const starWarsSpecies: string[] = [ 'Togruta', ]; -export const starWarsNouns: string[] = starWarsPlanets.concat(starWarsSpecies); \ No newline at end of file +export const starWarsNouns: string[] = starWarsPlanets.concat(starWarsSpecies); diff --git a/src/util/expressUtils.ts b/src/util/expressUtils.ts index 927f89d1..7f95e9f5 100644 --- a/src/util/expressUtils.ts +++ b/src/util/expressUtils.ts @@ -9,10 +9,10 @@ export type ControllerKey = { export const executeController = ( container: Container, - controllerName: K + controllerName: K, ): (req: Request, res: Response) => void | Promise => { return (req: Request, res: Response) => { const controller: ControllerInterface = container.get(controllerName); return controller.execute(req, res); - } -} + }; +}; diff --git a/src/util/latestOnlyTaskQueue.ts b/src/util/latestOnlyTaskQueue.ts index 71c0c45f..56c8e700 100644 --- a/src/util/latestOnlyTaskQueue.ts +++ b/src/util/latestOnlyTaskQueue.ts @@ -1,16 +1,16 @@ import { SequentialTaskQueue, CancellationToken, CancellablePromiseLike } from '@timesplinter/sequential-task-queue'; export default class LatestOnlyTaskQueue { - private queue = new SequentialTaskQueue(); + private queue = new SequentialTaskQueue(); - public run( - task: (token: CancellationToken) => Promise - ): CancellablePromiseLike { - void this.queue.cancel(); - return this.queue.push(task); - } + public run( + task: (token: CancellationToken) => Promise, + ): CancellablePromiseLike { + void this.queue.cancel(); + return this.queue.push(task); + } - public cancel(reason?: unknown): Promise { - return this.queue.cancel(reason); - } -} \ No newline at end of file + public cancel(reason?: unknown): Promise { + return this.queue.cancel(reason); + } +} diff --git a/src/util/numbers.ts b/src/util/numbers.ts index 363891ea..835111b0 100644 --- a/src/util/numbers.ts +++ b/src/util/numbers.ts @@ -8,7 +8,7 @@ export const Int = { } return value as Int; }, - ZERO: 0 as Int + ZERO: 0 as Int, }; export type Float = number & { __float__: never }; @@ -20,5 +20,5 @@ export const Float = { } return value as Float; }, - ZERO: 0 as Float + ZERO: 0 as Float, }; diff --git a/src/util/objects.ts b/src/util/objects.ts index 8ea7effc..37b1f4c8 100644 --- a/src/util/objects.ts +++ b/src/util/objects.ts @@ -3,8 +3,8 @@ type StringKey = Extract; export const getTypedKeys = (obj: T): (StringKey)[] => { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions return Object.keys(obj) as StringKey[]; -} +}; export const hasProperty = (obj: unknown, property: K): obj is Record => { return typeof obj === 'object' && obj !== null && property in obj; -} +}; diff --git a/src/util/process.ts b/src/util/process.ts index 552a63ea..d2ead146 100644 --- a/src/util/process.ts +++ b/src/util/process.ts @@ -23,11 +23,10 @@ export function spawnProcess(command: string, args: readonly string[], options: export function spawnProcess(command: string, args: readonly string[], options: SpawnOptionsWithStdioTuple): Promise>; export function spawnProcess(command: string, args: readonly string[], options: SpawnOptionsWithStdioTuple): Promise>; export function spawnProcess(command: string, args: readonly string[], options: SpawnOptions): Promise; - export function spawnProcess( command: string, args?: readonly string[], - options?: SpawnOptions + options?: SpawnOptions, ): Promise { return new Promise((resolve, reject) => { let settled = false; @@ -39,10 +38,18 @@ export function spawnProcess( if ('code' in e) { switch (e.code) { - case 'ENOENT': e.message = `Executable '${command}' not found`; break; - case 'EACCES': e.message = 'Permission denied'; break; - case 'EISDIR': e.message = 'Is a directory, not an executable'; break; - case 'EMFILE': e.message = 'Too many open files'; break; + case 'ENOENT': + e.message = `Executable '${command}' not found`; + break; + case 'EACCES': + e.message = 'Permission denied'; + break; + case 'EISDIR': + e.message = 'Is a directory, not an executable'; + break; + case 'EMFILE': + e.message = 'Too many open files'; + break; } } From b361df6b8a47179da4bc46f1f0907e5143b8ed67 Mon Sep 17 00:00:00 2001 From: HRS Date: Wed, 5 Aug 2026 23:29:57 +0200 Subject: [PATCH 02/15] no-explicit-any and knip --- .babelrc | 10 - .github/workflows/test.yml | 7 +- eslint.config.ts | 27 +- knip.json | 5 + package-lock.json | 9927 +++++++---------- package.json | 17 +- src/app.ts | 2 +- src/automation/scriptRuntime.ts | 6 +- src/automation/scriptVm.ts | 3 +- src/automation/scriptVmFactory.ts | 4 +- .../automation/createScriptController.ts | 2 +- .../automation/runScriptController.ts | 2 +- src/controller/patchDeviceController.ts | 2 +- src/device/attribute/deviceAttribute.ts | 2 +- src/device/attribute/listDeviceAttribute.ts | 3 +- src/device/device.ts | 12 +- src/device/deviceConfig.ts | 5 +- src/device/deviceManager.ts | 4 +- src/device/peripheralDevice.ts | 6 +- src/device/protocol/deviceProtocol.ts | 12 +- src/device/protocol/messageResponseHandler.ts | 19 +- .../protocol/slvCtrlPlus/slvCtrlPlusDevice.ts | 3 +- .../virtual/audio/piperVirtualDeviceConfig.ts | 3 +- .../virtual/audio/ttsVirtualDeviceConfig.ts | 3 +- .../virtual/audio/ttsVirtualDeviceLogic.ts | 2 +- .../virtual/genericVirtualDeviceFactory.ts | 16 +- .../genericVirtualDeviceLogicFactory.ts | 9 +- .../randomGeneratorVirtualDeviceConfig.ts | 3 +- src/device/protocol/virtual/virtualDevice.ts | 14 +- .../protocol/virtual/virtualDeviceFactory.ts | 4 +- .../protocol/virtual/virtualDeviceLogic.ts | 6 +- .../virtual/virtualDeviceLogicFactory.ts | 6 +- .../protocol/virtual/virtualDeviceProvider.ts | 6 +- src/device/protocol/zc95/zc95Protocol.ts | 2 +- src/device/provider/deviceProviderEvent.ts | 0 .../provider/genericDeviceProviderFactory.ts | 17 +- src/device/serializedTypes.ts | 24 +- .../genericDeviceAttributeDiscriminator.ts | 18 - .../discriminator/objectDiscriminator.ts | 6 +- src/serviceProvider/deviceServiceProvider.ts | 148 +- src/settings/serializedTypes.ts | 4 +- src/socket/types.ts | 4 +- src/util/async.ts | 4 +- src/util/devNullStream.ts | 5 +- src/util/dictionary.ts | 4 +- .../helpers/airoticDeviceSimulator.ts | 2 +- tests/integration/helpers/appHelper.ts | 37 +- .../helpers/buttplugIoServerSimulator.ts | 4 +- .../helpers/mockSerialPortFactory.ts | 2 +- 49 files changed, 4064 insertions(+), 6369 deletions(-) delete mode 100644 .babelrc create mode 100644 knip.json delete mode 100644 src/device/provider/deviceProviderEvent.ts delete mode 100644 src/serialization/discriminator/genericDeviceAttributeDiscriminator.ts diff --git a/.babelrc b/.babelrc deleted file mode 100644 index 194cd0c0..00000000 --- a/.babelrc +++ /dev/null @@ -1,10 +0,0 @@ -{ - "presets": [ - ["@babel/preset-env", {"targets": {"node": "current"}}], - "@babel/preset-typescript" - ], - "plugins": [ - ["@babel/plugin-proposal-decorators", { "legacy": true }], - ["@babel/plugin-proposal-class-properties", { "loose": true }] - ] -} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b7f3196a..7bee439a 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -40,12 +40,15 @@ jobs: node-version: ${{ matrix.node-version }} - name: Install modules run: npm ci + - name: Typecheck + run: | + npm run typecheck - name: Linting run: | npm run lint - - name: Typecheck + - name: Checking for dead code run: | - npm run typecheck + npm run knip - name: Unit tests with coverage run: | npm run test:coverage diff --git a/eslint.config.ts b/eslint.config.ts index 1c244213..704a0292 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -76,7 +76,7 @@ export default [ "accessibility": "explicit" } ], - "@typescript-eslint/explicit-module-boundary-types": "off", + "@typescript-eslint/explicit-module-boundary-types": "error", "@typescript-eslint/naming-convention": [ "error", { @@ -92,7 +92,7 @@ export default [ ], "@typescript-eslint/no-empty-function": "error", "@typescript-eslint/no-empty-interface": "off", - "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-floating-promises": [ "warn", { @@ -172,9 +172,10 @@ export default [ prev: ["type", "interface", "class", "enum"], next: "export", }], - "quotes": ["error", "single", { "allowTemplateLiterals": true }], - "object-curly-spacing": ["warn", "always"], - "complexity": "off", + "@jsdoc/check-alignment": "error", + "@jsdoc/check-indentation": "error", + "@jsdoc/tag-lines": "error", + "complexity": "error", "constructor-super": "error", "dot-notation": "off", "eqeqeq": [ @@ -195,14 +196,10 @@ export default [ "undefined" ], "id-match": "error", - "@jsdoc/check-alignment": "error", - "@jsdoc/check-indentation": "error", - "@jsdoc/tag-lines": "error", "max-classes-per-file": [ "error", 1 ], - "new-parens": "error", "no-bitwise": "error", "no-caller": "error", "no-cond-assign": "error", @@ -216,7 +213,6 @@ export default [ "no-new-wrappers": "error", "no-shadow": "off", "no-throw-literal": "error", - "no-trailing-spaces": "error", "no-undef-init": "error", "no-underscore-dangle": [ "error", @@ -225,7 +221,7 @@ export default [ } ], "no-unsafe-finally": "error", - "no-unused-expressions": "off", + "no-unused-expressions": "error", "no-unused-labels": "error", "no-use-before-define": "off", "no-implicit-coercion": ["error", { @@ -241,15 +237,6 @@ export default [ ], "prefer-const": "error", "radix": "error", - "spaced-comment": [ - "warn", - "always", - { - "markers": [ - "/" - ] - } - ], "use-isnan": "error", "valid-typeof": "off", }, diff --git a/knip.json b/knip.json new file mode 100644 index 00000000..cc6652f9 --- /dev/null +++ b/knip.json @@ -0,0 +1,5 @@ +{ + "ignoreDependencies": [ + "@serialport/bindings-cpp" + ] +} diff --git a/package-lock.json b/package-lock.json index 6db3eb54..1de27669 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,8 +6,10 @@ "": { "license": "LGPL-3.0-only", "dependencies": { + "@serialport/bindings-interface": "^1.2.2", + "@serialport/stream": "^13.0.0", "@sinclair/typebox": "^0.34.47", - "@stoprocent/noble": "^2.3.17", + "@stoprocent/noble": "^2.7.1", "@timesplinter/pimple": "^2.1.1", "@timesplinter/sequential-task-queue": "^1.4.0", "ajv": "^8.17.1", @@ -30,33 +32,28 @@ "socket.io": "^4.8.3", "speaker": "https://github.com/SlvCtrlPlus/node-speaker/releases/download/v0.1.0/speaker-v0.1.0.tgz", "sucrase": "^3.35.1", - "type-detect": "^4.1.0", "unique-names-generator": "^4.7.1", "usb": "^3.0.0", "uuid": "^14.0.0" }, "devDependencies": { - "@babel/core": "^7.22.8", - "@babel/node": "^7.22.6", - "@babel/plugin-proposal-decorators": "^7.22.7", - "@babel/preset-env": "^7.14.4", - "@babel/preset-typescript": "^7.22.5", "@eslint/js": "^10.0.1", - "@serialport/binding-mock": "^10.2.2", "@stylistic/eslint-plugin": "^5.10.0", "@types/cors": "^2.8.10", "@types/express": "^5.0.6", "@types/node": "^24.1.0", "@types/supertest": "^7.2.0", "@types/type-detect": "^4.0.3", + "@types/w3c-web-usb": "^1.0.14", "@types/ws": "^8.18.1", "@vitest/coverage-v8": "^4.0.16", + "@vitest/expect": "^4.1.10", "eslint": "^10.1.0", - "eslint-config-prettier": "^10.1.8", "eslint-plugin-jsdoc": "^62.8.0", "eslint-plugin-prefer-arrow-functions": "^3.10.1", "globals": "^17.0.0", "jiti": "^2.6.1", + "knip": "^6.31.0", "nodemon": "^3.0.2", "pino-pretty": "^13.1.3", "socket.io-client": "^4.8.3", @@ -71,2531 +68,2045 @@ "node": ">=24.0.0 <25.0.0" } }, - "node_modules/@babel/code-frame": { + "node_modules/@babel/helper-string-parser": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", - "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-validator-identifier": "^7.29.7", - "js-tokens": "^4.0.0", - "picocolors": "^1.1.1" - }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/compat-data": { + "node_modules/@babel/helper-validator-identifier": { "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", - "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/core": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", - "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helpers": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" + "@babel/types": "^7.29.8" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "node_modules/@babel/generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", - "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" + "bin": { + "parser": "bin/babel-parser.js" }, "engines": { - "node": ">=6.9.0" + "node": ">=6.0.0" } }, - "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.29.7.tgz", - "integrity": "sha512-OoK6239jHPuSQOoS0kfTVKn0b/rVTk0seKq4Gd2UMLtmOVLjDC0ki3e+c90Trqv2gMfvJFqkiljrr568+qddiw==", + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.7" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", - "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "node_modules/@bcoe/v8-coverage": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-1.0.2.tgz", + "integrity": "sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, "engines": { - "node": ">=6.9.0" + "node": ">=18" } }, - "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.29.7.tgz", - "integrity": "sha512-IY3ZD9Tmooqr3TUhc3DUWxiuo8xx1DWLhd5M7hQ+ZWJamqM2BbalrBJb2MisSLoYorOj75U03qULCxQTY9r3hg==", + "node_modules/@emnapi/core": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.2.tgz", + "integrity": "sha512-TC8MkTuZUtcTSiFeuC0ksCh9QIJ5+F21MvZ4Wn4ORfYaFJ/0dsiudv5tVkejgwZlwQ39jL9WWDe2lz8x0WglOA==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-member-expression-to-functions": "^7.29.7", - "@babel/helper-optimise-call-expression": "^7.29.7", - "@babel/helper-replace-supers": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", - "@babel/traverse": "^7.29.7", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" } }, - "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.29.7.tgz", - "integrity": "sha512-907Uymvqgg1dwUA+7IGwFAOSYzQOuzPXKNJ1yxzwPffzkYFg2q2eHi1fIOs6sXkG9NbIUMunnUlkYsfRFNvomg==", + "node_modules/@emnapi/runtime": { + "version": "1.11.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.2.tgz", + "integrity": "sha512-kyOl3X0DuTiT1h2ft8r2fYO8JYtU9a9Xis/zBSiGArNaagCOWx90N1k2wxp18czFDH+OgcWGb5ZP/XMt3dcyPA==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "regexpu-core": "^6.3.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "tslib": "^2.4.0" } }, - "node_modules/@babel/helper-define-polyfill-provider": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.6.8.tgz", - "integrity": "sha512-47UwBLPpQi1NoWzLuHNjRoHlYXMwIJoBf7MFou6viC/sIHWYygpvr0B6IAyh5sBdA2nr2LPIRww8lfaUVQINBA==", + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-plugin-utils": "^7.28.6", - "debug": "^4.4.3", - "lodash.debounce": "^4.0.8", - "resolve": "^1.22.11" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/@babel/helper-globals": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", - "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" + "tslib": "^2.4.0" } }, - "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.29.7.tgz", - "integrity": "sha512-j+7JYmk1JYDtACIGj0QJqqWZjoUpMoEikQGADMaHgCMCSDqd2+P32rfcibUNrGOMWrlzK1WJBdxrB3JJQZwWtg==", + "node_modules/@es-joy/jsdoccomment": { + "version": "0.86.0", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.86.0.tgz", + "integrity": "sha512-ukZmRQ81WiTpDWO6D/cTBM7XbrNtutHKvAVnZN/8pldAwLoJArGOvkNyxPTBGsPjsoaQBJxlH+tE2TNA/92Qgw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" + "@types/estree": "^1.0.8", + "@typescript-eslint/types": "^8.58.0", + "comment-parser": "1.4.6", + "esquery": "^1.7.0", + "jsdoc-type-pratt-parser": "~7.2.0" }, "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@babel/helper-module-imports": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", - "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "node_modules/@es-joy/resolve.exports": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@es-joy/resolve.exports/-/resolve.exports-1.2.0.tgz", + "integrity": "sha512-Q9hjxWI5xBM+qW2enxfe8wDKdFWMfd0Z29k5ZJnuBqD/CasY5Zryj09aCA6owbGATWz+39p5uIdaHXpopOcG8g==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" - }, "engines": { - "node": ">=6.9.0" + "node": ">=10" } }, - "node_modules/@babel/helper-module-transforms": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", - "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "node_modules/@eslint-community/eslint-utils": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.10.1.tgz", + "integrity": "sha512-cuadcxVFE8sDK6iWJbs8Sn0av2Nrh2QSGQhVlBW9AaAHqHwjWsZHT8LJ4hFGPh7ASBV2deFdM7H/DPjulmh8rg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" + "eslint-visitor-keys": "^3.4.3" }, "engines": { - "node": ">=6.9.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, - "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.29.7.tgz", - "integrity": "sha512-+kmGVjcT9RGYzoDwdwEqEvGgKe3BYq+O1iGzjFubaNgZHwYHP6lsF2Yghf4kEuv9BV7tYDZ913aBW9am6YKong==", + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.7" - }, + "license": "Apache-2.0", "engines": { - "node": ">=6.9.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", - "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, - "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.29.7.tgz", - "integrity": "sha512-16AMiW26DbXWBbr3B8wNozKM0ydMLB892vaOaJW/fPJdnT8vJk5sdkQcU/isqUxyCE0cEoa8wZOcbgDuC4b6Og==", + "node_modules/@eslint/config-array": { + "version": "0.23.5", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", + "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-wrap-function": "^7.29.7", - "@babel/traverse": "^7.29.7" + "@eslint/object-schema": "^3.0.5", + "debug": "^4.3.1", + "minimatch": "^10.2.4" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@babel/helper-replace-supers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.29.7.tgz", - "integrity": "sha512-atfGXWSeCiF4DnKZIfmJfQRkSw9b9gNNXR1kqKjbhG4pGYCOnkp8OcTB8E3NXjBu8NpheSnOeNKz8KT7UNFTmQ==", + "node_modules/@eslint/config-helpers": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.7.0.tgz", + "integrity": "sha512-DObd/KKUsU+FaFv4PLxSRenpXfQWmPXXP3pPZ6/K1PCrMu2vQpMDMuQe/BqYeoLcz8ro0bVDF1RxOJgfVEdhUw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/helper-member-expression-to-functions": "^7.29.7", - "@babel/helper-optimise-call-expression": "^7.29.7", - "@babel/traverse": "^7.29.7" + "@eslint/core": "^1.2.1" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.29.7.tgz", - "integrity": "sha512-brcMGQaVzIeUb+6/bs1Av0f8YuNNjKY2JyvfRCsFuFsdKccEQ5Ges2y74D74NZ1Rz8lKJ9ksJkfqwQFJ/iNEyQ==", + "node_modules/@eslint/core": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", + "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" + "@types/json-schema": "^7.0.15" }, "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@babel/helper-string-parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", - "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@babel/helper-validator-identifier": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", - "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "node_modules/@eslint/js": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", + "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", "dev": true, "license": "MIT", "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "eslint": "^10.0.0" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "node_modules/@babel/helper-validator-option": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", - "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "node_modules/@eslint/object-schema": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", + "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@babel/helper-wrap-function": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.29.7.tgz", - "integrity": "sha512-iES0Skag9ERIF68aXadpO6dbXa03mNWK3sEqJaMnLNs/eC3l0lkImdfoy6Y09/SfkpawdAB4RjQ7PVA7TcVGdw==", + "node_modules/@eslint/plugin-kit": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", + "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/template": "^7.29.7", - "@babel/traverse": "^7.29.7", - "@babel/types": "^7.29.7" + "@eslint/core": "^1.2.1", + "levn": "^0.4.1" }, "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.13.0 || >=24" } }, - "node_modules/@babel/helpers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", - "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "node_modules/@humanfs/core": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", + "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7" + "@humanfs/types": "^0.15.0" }, "engines": { - "node": ">=6.9.0" + "node": ">=18.18.0" } }, - "node_modules/@babel/node": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/node/-/node-7.29.7.tgz", - "integrity": "sha512-nfdPXz8/mD3/t+1nE1DKwGR14Ccjt5xeF7u3g7sqWnLi4yR6n+9Z0kThIROF8SRM07ZKpEtiWSKpWKxsMiJeew==", + "node_modules/@humanfs/node": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", + "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", "dev": true, - "license": "MIT", + "license": "Apache-2.0", "dependencies": { - "@babel/register": "^7.29.7", - "commander": "^6.2.0", - "core-js": "^3.48.0", - "node-environment-flags": "^1.0.5", - "regenerator-runtime": "^0.14.0", - "v8flags": "^3.1.1" - }, - "bin": { - "babel-node": "bin/babel-node.js" + "@humanfs/core": "^0.19.2", + "@humanfs/types": "^0.15.0", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">=18.18.0" } }, - "node_modules/@babel/parser": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", - "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "node_modules/@humanfs/types": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", + "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.7" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, + "license": "Apache-2.0", "engines": { - "node": ">=6.0.0" + "node": ">=18.18.0" } }, - "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.29.7.tgz", - "integrity": "sha512-j8SrR0zLZrRsC09DlszEx8FpMiwukKffYXMK0d5LmOglO7vGG6sz/BR/20yHqWH+Lnn31JTt2PE3hIWNgM2J6w==", + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, + "license": "Apache-2.0", "engines": { - "node": ">=6.9.0" + "node": ">=12.22" }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@babel/plugin-bugfix-safari-class-field-initializer-scope": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-class-field-initializer-scope/-/plugin-bugfix-safari-class-field-initializer-scope-7.29.7.tgz", - "integrity": "sha512-r8j8escF+U2FUHo0KOhPUdMzUO+jp9fInva6+ACVAF3Y97Ev+5iNZwiqTghmzNeWwDkOPlYuTcfb1vDaoZKmAQ==", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "license": "Apache-2.0", "engines": { - "node": ">=6.9.0" + "node": ">=18.18" }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.29.7.tgz", - "integrity": "sha512-GE1TFSiuFeGsCxmYXZl8HwoPrVlwe4rHPFE8weieGKZqnDORK+Ar3vgWMgW+AOxQ6/2TgLSKx9p6W7O4rC6qgQ==", - "dev": true, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" } }, - "node_modules/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-rest-destructuring-rhs-array/-/plugin-bugfix-safari-rest-destructuring-rhs-array-7.29.7.tgz", - "integrity": "sha512-oBNVCvnO5tND+xSopWvV8WNGfpTfgP4Zr/YXXSj8zfmcPktp5Ku/aZlsIowgSD4fjmgHn6sGmB9APVsU5zOdhA==", - "dev": true, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" - }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.29.7.tgz", - "integrity": "sha512-QQt9qKHZ2sg/kivaLr7lnQr8HVrQDdBNSfCsTjiDxRuX/K5ORyKq+Bu8Xr0cDE3Dfkv0cw28Ve0EKyKMvulkOw==", - "dev": true, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", - "@babel/plugin-transform-optional-chaining": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.13.0" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.29.7.tgz", - "integrity": "sha512-pn6QacGLgvCcwc+syUhKE/qSjV2D1IHDB84RNxWYSt1mW3K/SCtjinZ2p0cETJxAWBjPy3K/1lHwG5BjjPxNlw==", + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.2.2.tgz", + "integrity": "sha512-JfB4kuJQjaoHuCTseIINHtHWeJnvgEcxjwA5t/Y00ZgaOO1Crz3fjT/p8kT28zA/Caz7oiUMn3d6H2yOVCVwuw==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/traverse": "^7.29.7" + "@tybys/wasm-util": "^0.10.3" }, "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || ^22.13.0 || >=23.5.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.3", + "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.3" } }, - "node_modules/@babel/plugin-proposal-decorators": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-decorators/-/plugin-proposal-decorators-7.29.7.tgz", - "integrity": "sha512-EtU0Hi3GvrTqD56xKmZvV/uCXK2ZbwVNPNLAquVItcAZpUhkXwWlo3Fmj0c2LxgSf2I8IDULeAepwNP1OefLXg==", + "node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/plugin-syntax-decorators": "^7.29.7" - }, "engines": { - "node": ">=6.9.0" + "node": "^14.21.3 || >=16" }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@babel/plugin-proposal-private-property-in-object": { - "version": "7.21.0-placeholder-for-preset-env.2", - "dev": true, + "node_modules/@node-usb/usb-darwin-arm64": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@node-usb/usb-darwin-arm64/-/usb-darwin-arm64-3.0.1.tgz", + "integrity": "sha512-GB+MGKaXvEtaDOWng7nCPxWvnXWhZtbFL0FW9ncDmGYtFl3OW+xfLZGhd5Y9VKG6GXXIYS3x1FsBI+zkcxVMhA==", + "cpu": [ + "arm64" + ], "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0" } }, - "node_modules/@babel/plugin-syntax-decorators": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-decorators/-/plugin-syntax-decorators-7.29.7.tgz", - "integrity": "sha512-9MTTLbF39X6sqM92JPEsoI7++26hjZvzkxKZy64aMhWLH2mPkJ/Q3AV4QLmls3R14FpSpkOwQQfUh962JGQxxg==", - "dev": true, + "node_modules/@node-usb/usb-darwin-x64": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@node-usb/usb-darwin-x64/-/usb-darwin-x64-3.0.1.tgz", + "integrity": "sha512-oTY8vLoigjYFwNft1nZNNje0zAXjSQy9u3swe+pcCfZp6qQbEoSLegLEBr94arEgQloQabaq4VA2JZVA8cUQqQ==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0" } }, - "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.29.7.tgz", - "integrity": "sha512-/An1OCBN93thpBAGyfsK2pcf0jvju1SAtKkL2Ny++B5Sy6sqgzXDQH1cZxWbF96Wuk+bn41MDA9bLd4VVAw6rw==", - "dev": true, + "node_modules/@node-usb/usb-linux-arm-gnueabihf": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@node-usb/usb-linux-arm-gnueabihf/-/usb-linux-arm-gnueabihf-3.0.1.tgz", + "integrity": "sha512-GN5dTxc/FhjmC9NP7Xx1esGAhpIhnNvAf02kk/0i7Cz0w1VaJ/I0XbIVAYIJ5zhDg/oSYkhdILHbOS9+mlWCRQ==", + "cpu": [ + "arm" + ], "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0" } }, - "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.29.7.tgz", - "integrity": "sha512-zGYcYfq/WmZ4V+kBIXQon9dSSc8ircGZqw9ZaNhhGj9nZkeBu1jHLBDQqYYi5WA9uawvA2sIMbry2nCFhf5Djg==", - "dev": true, + "node_modules/@node-usb/usb-linux-arm64-gnu": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@node-usb/usb-linux-arm64-gnu/-/usb-linux-arm64-gnu-3.0.1.tgz", + "integrity": "sha512-7cL85qb/yrBXvssZwL3k62duBxT/LaCY/Vcfv/Du9MLFdHhqU0zdYX5fLch/zaW5euYXQE0gmCoTfimfAjlxGQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0" } }, - "node_modules/@babel/plugin-syntax-jsx": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.29.7.tgz", - "integrity": "sha512-TSu8+mHCoEaaCDEZ0I3+6mvTBYR4PCxQwf2z9/r5Tbztv6NaLR3B9thGTTxX2WGuGHJqRiAbKPeGTJ5XWXVg6A==", - "dev": true, + "node_modules/@node-usb/usb-linux-arm64-musl": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@node-usb/usb-linux-arm64-musl/-/usb-linux-arm64-musl-3.0.1.tgz", + "integrity": "sha512-EweA4HeTwzLiRHsU2DRFflJ3vx0shB5Z9Lt5/P/bkoUtSNt8uGjFCmsCjqoO068ul9oejsBMJg22rNLD3+iFqQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0" } }, - "node_modules/@babel/plugin-syntax-typescript": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.29.7.tgz", - "integrity": "sha512-ngr+82Sh0xMz25TPCZi+nC2iTzjfCdWS2ONXTp/PtSCHCgaCNBpdMqgvJ2ccdLlClVZ7sisIgB914j/JFe+RZA==", - "dev": true, + "node_modules/@node-usb/usb-linux-x64-gnu": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@node-usb/usb-linux-x64-gnu/-/usb-linux-x64-gnu-3.0.1.tgz", + "integrity": "sha512-cEE7GyMhtfq5OM/MXZn3RmYBqPvId0CMuoBx7biazPvlnf0iEWYjpma0WvVMWad/f1QIcC45WgrazHaVv6gAPw==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0" } }, - "node_modules/@babel/plugin-syntax-unicode-sets-regex": { - "version": "7.18.6", - "dev": true, + "node_modules/@node-usb/usb-linux-x64-musl": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@node-usb/usb-linux-x64-musl/-/usb-linux-x64-musl-3.0.1.tgz", + "integrity": "sha512-zGWlk1mzElTPO4wxlRcd1C7xd7xDxzgpOXbQfE+NbClOUJ71NkbnahWN2ef2LcJvFESYg/CV9Dv/ULKSpJWEEw==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.18.6", - "@babel/helper-plugin-utils": "^7.18.6" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0" } }, - "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.29.7.tgz", - "integrity": "sha512-N7zArUXWzAMzm+/N0uPBeVB3Fam5lMxtUwMmDK5f/IBBS7a7p1qeUoxd/6CckXoxUdgsntq1Dh8xNW06maZbDQ==", - "dev": true, + "node_modules/@node-usb/usb-win32-arm64-msvc": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@node-usb/usb-win32-arm64-msvc/-/usb-win32-arm64-msvc-3.0.1.tgz", + "integrity": "sha512-ayXNeb+KvdkZLXzTWyttNONUPG8/x9PwuP+GvUeiYCQuyLMqmbGeMsO7IiuBWTnxLFtCdep+UBpcL8XMcYdTiA==", + "cpu": [ + "arm64" + ], "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0" } }, - "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.29.7.tgz", - "integrity": "sha512-d98gXZkgswvkyohMBABkhm3GeXhYj8psWfwQ2C7gtfrKGTykQa/iOIi+JJhwMjPlZ6Vm2XN+DCf3Es1EoG4ZLA==", - "dev": true, + "node_modules/@node-usb/usb-win32-ia32-msvc": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@node-usb/usb-win32-ia32-msvc/-/usb-win32-ia32-msvc-3.0.1.tgz", + "integrity": "sha512-U5281i6yY7gtoTwLom8nLF2o+ILI/g4OFpbAJBr4GbVPOTu+CSNF2Ea+dLEAG30148mp8v79NEsIiAbWXS/1aw==", + "cpu": [ + "ia32" + ], "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-remap-async-to-generator": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0" } }, - "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.29.7.tgz", - "integrity": "sha512-pcUb2SS+RMo9TWVBwKGI5ShtoG7R+zBsFmCKDa6fe8c+hPr3XJlZgoE5j6i8W7gDjhyvy+85vmYexanvXh3d1w==", - "dev": true, + "node_modules/@node-usb/usb-win32-x64-msvc": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@node-usb/usb-win32-x64-msvc/-/usb-win32-x64-msvc-3.0.1.tgz", + "integrity": "sha512-T1jzCBwgnA6E5otxJS/j44b//6fGzHSDmLQtNN1fX6JvdQIisgtbf2+iKD/YI7E247j6Yi9Ixt+MKjHt64hugA==", + "cpu": [ + "x64" + ], "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-remap-async-to-generator": "^7.29.7" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": ">= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0" } }, - "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.29.7.tgz", - "integrity": "sha512-cUSmjh72N+rN4PrkFlN1dJwNCwjVp5d38/CQrEsFggkD10UiFlBFgdH3tv5dNsLuHY+3S8db2xCHjhZcv5WgvA==", + "node_modules/@oxc-parser/binding-android-arm-eabi": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm-eabi/-/binding-android-arm-eabi-0.142.0.tgz", + "integrity": "sha512-ZiRGDutGsv1G6bL/ozy/koC0Sv39T1DqyoC4KD1DOy9ZoACm1O5UWhEK2c02Qdk+4lfLVkvFa/mQ0fm/4h1BtQ==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.29.7.tgz", - "integrity": "sha512-ONyr4+AZhKh8yKWInVxU9AXA9EbsyeLcL6V0dJy6M2/62vuvpGm29zzuymbTpdc451GEpDIdAyPLP3r+P61yKQ==", + "node_modules/@oxc-parser/binding-android-arm64": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-android-arm64/-/binding-android-arm64-0.142.0.tgz", + "integrity": "sha512-WZkvGRLNQTz8lR9zP5nLjUdlroRCopBu3g9zF1p/laE6DzT1UbQo8Rdz5MWhaJUPYg/6gp+jo7HUgsyKaN1FtQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.29.7.tgz", - "integrity": "sha512-GtcpjFvanPfzNQi3eTitsCqtRRmmqzpy/A+yhTR1HaZo1Ly3EA8ZXxlPyHdR8/IuRMYc3E4wdGBewB2QKQjAaA==", + "node_modules/@oxc-parser/binding-darwin-arm64": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-arm64/-/binding-darwin-arm64-0.142.0.tgz", + "integrity": "sha512-l4khS8LQOOVYsGRVARo1gSaCT/aBSceUVXgtovWc2+drnxVuDr082WA3OCHVdVzIz5JIrP/y9CWsSKxBDNmYGg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.29.7.tgz", - "integrity": "sha512-kibJgmEdX2iMwsHY2tSZNDgj8PwIlCQz7FK9KuGKO8zsuoUwSEhoNnNVp/emKWrbY4HeO6kkXfdMqRKKKXBm2A==", + "node_modules/@oxc-parser/binding-darwin-x64": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-darwin-x64/-/binding-darwin-x64-0.142.0.tgz", + "integrity": "sha512-QBsNF3nqlXmcH2B1YOPqQYmCJoy4HuIjUxGbBO/k5JAJUl68ghU2psRY2zPk+RyBaWqKP/qfL4oaFgEMCdwskA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.12.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-classes": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.29.7.tgz", - "integrity": "sha512-qV0OGGBVacduzQHE649JyCneOFI/maT+YKsO+K4Yi3xv2wTPNjM/W2o2gdzMwEAZz7fXNTHAe0NcSg30bIN69g==", + "node_modules/@oxc-parser/binding-freebsd-x64": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-freebsd-x64/-/binding-freebsd-x64-0.142.0.tgz", + "integrity": "sha512-b7Q7m4Cqc6XqNhri3R+QhU+GVy646Pn+bkdhrDdWym/Fdi0ZUa+d73H9dm5H91JtbtAQ/z1d8XKMW3oOV8a4tQ==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-globals": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-replace-supers": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.29.7.tgz", - "integrity": "sha512-RK7/IyU5phpuCdBAuig5VkzG/EnbDaui5SQGdU9BFrHdV+mV4cUjLMQ9lJDjLNtWHsqtiefpGZUXQP2BiTYMsA==", + "node_modules/@oxc-parser/binding-linux-arm-gnueabihf": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-0.142.0.tgz", + "integrity": "sha512-3riVS5IhdH3uCZj1Y9ftDQlR0dvLsIlw/edrRqk8JhgNd5K0XSs+UBtgh50N13CAlW9/TXj6sVGXaKNBocd0Yg==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/template": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.29.7.tgz", - "integrity": "sha512-iPX8aD6H9zV5s7ZsqTdNocPN/MGQ5sSMnElKrktxjJRMnB2jN/1p2+R7GkfD6CAYoVFqy5A4XnSIUeGgJzIWpg==", + "node_modules/@oxc-parser/binding-linux-arm-musleabihf": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-0.142.0.tgz", + "integrity": "sha512-NmXUOpgpTSkhl795TiXmWppTwmSJ92RC1qvD6e4XOF+slgmo3e6Ah+kEu+6AN8s7NAOEwqGmir58MgSQSWmBSA==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.29.7.tgz", - "integrity": "sha512-3qc18hsD2RdZiyJNDNc7HQpv6xbncwh8FYtxNFFzclSyh/trPD9KkVR9BDECUjDLvb7yJVF15GfYUuC+LMkkiQ==", + "node_modules/@oxc-parser/binding-linux-arm64-gnu": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-0.142.0.tgz", + "integrity": "sha512-gc0EXsKtXgerujmU2Bql3u1L1HsSQ2774R83idq/FoNMPVV/RY/1ErFsvnit7KoiP/sLvzQixeUo4Ut0ic0wmw==", + "cpu": [ + "arm64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.29.7.tgz", - "integrity": "sha512-6IvRRriEMqnBwD6chtxdLpMYCHWEzN+oL5cyQtjykya19UgzbmKhxmhZgKC/LHxS2nYr9Q/qYPZ5Lr6jOL9+yQ==", + "node_modules/@oxc-parser/binding-linux-arm64-musl": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-arm64-musl/-/binding-linux-arm64-musl-0.142.0.tgz", + "integrity": "sha512-F2XvmWSE0uWpie+jHKKIFgdVOe9ypGhkEZxKx5DuW215K6cbAC274yYaPkcM7EqY4Df3Weyhpcz3lsURyH2LVg==", + "cpu": [ + "arm64" + ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-duplicate-named-capturing-groups-regex": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-named-capturing-groups-regex/-/plugin-transform-duplicate-named-capturing-groups-regex-7.29.7.tgz", - "integrity": "sha512-2wiIyo2BjtgU7HufSeDnL9L2O7zr8jmhFKuSr65VpRkUiRKRNpb0mdlk56+XPPKoIrfHqzbMuglDvZun0RISsA==", + "node_modules/@oxc-parser/binding-linux-ppc64-gnu": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-0.142.0.tgz", + "integrity": "sha512-wLMbT21U/QxknQsk+VvNF0b9D2/aGWhcaQQQ+VYlE8FwD5+GoWZIPPXNzyHmkYyhm0KB3itL+TBavjMatqNnYA==", + "cpu": [ + "ppc64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.29.7.tgz", - "integrity": "sha512-giOlEm/EFjfjr+te9NsdjkUo2v4f8rS/SXPumRVHAtbNcyNlvtREkU1dZzaIDclNpnaVhlCqRdFKhJBjBikzLg==", + "node_modules/@oxc-parser/binding-linux-riscv64-gnu": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-0.142.0.tgz", + "integrity": "sha512-+G8F/4ckwT7FCJV4H2bt09xEzJbjNCfuL4Sp1AYNaFtFMVtgIGMuJlteT82U+K0UIZ/DzAR/LDlMFnEuajG7Kw==", + "cpu": [ + "riscv64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-explicit-resource-management": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-explicit-resource-management/-/plugin-transform-explicit-resource-management-7.29.7.tgz", - "integrity": "sha512-Rstj7coNz8sE+7Ju7ihpHLI564lsK5pUpNNlvptCIC/16E/S5hbl6n3kESPKdNRmqEWlpn5xpS5Q2dvXBsySLw==", + "node_modules/@oxc-parser/binding-linux-riscv64-musl": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-0.142.0.tgz", + "integrity": "sha512-hTsHtTLxMAfCo+rpF5K3qZJKW2NpPN/CHd4mYB3y7XlSdspHkd2gehDIofP64AacA9nWQw2tY3O7wR6UY8IVOA==", + "cpu": [ + "riscv64" + ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/plugin-transform-destructuring": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.29.7.tgz", - "integrity": "sha512-zFpMOTLZBdW5LfObqcSbL6kefg4R4eLdmvS0wbN9M6D5Mym/sKm9toOoWyVOa+xDjvCnuWcHls2YonXwHvH3CQ==", + "node_modules/@oxc-parser/binding-linux-s390x-gnu": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-0.142.0.tgz", + "integrity": "sha512-6y7qYY3TCUDYjqswImdTGl92y+KA/80twALegQPN27kfY+bG7Ib1+L3jbmrCZQx6wrVnai9IPsEZp07I0hx7JQ==", + "cpu": [ + "s390x" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.29.7.tgz", - "integrity": "sha512-24B2nOy2TeJSMheqwPD4DDQOV/elLSIlKxjZt4i05H5AgdPdWR3n18HnNrcJ+j76WJd9gbwb9jPjNYUy6RautA==", + "node_modules/@oxc-parser/binding-linux-x64-gnu": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-gnu/-/binding-linux-x64-gnu-0.142.0.tgz", + "integrity": "sha512-i69kAWU+2LgoH5bR+zWiiu+UzAw7Oxkwv7COeJTeY19pn4e70nKQcr9Pm6cL2Z0Z54d+gl9qADlK/0yyuCPiBA==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-for-of": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.29.7.tgz", - "integrity": "sha512-zeSIHh0+E1Um1WJRXCFlHQYu2ieJNdivLLjlBEp+dIBu3S51n+SZZmIXjxnItw6pz56Cn+KvK68BIBVsxq2JiQ==", + "node_modules/@oxc-parser/binding-linux-x64-musl": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-linux-x64-musl/-/binding-linux-x64-musl-0.142.0.tgz", + "integrity": "sha512-4SQs678MmjYVrmhAgCWD4o0vpaFszXw9xLX5p2Z9MMFcltxiLkA88wQjh80YHjPrXtpyZ2CWI5m+1yNKM0m2Pw==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-function-name": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.29.7.tgz", - "integrity": "sha512-otRWaHXE6fbAGkePvaj/kvs3HsqXfPhlnzwSOlnFgbqCPMd975dW+4wZ00WFBt+/YlBGcJwNrARQTOJOb4ZrIg==", + "node_modules/@oxc-parser/binding-openharmony-arm64": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-openharmony-arm64/-/binding-openharmony-arm64-0.142.0.tgz", + "integrity": "sha512-YHpx9N7Ln3a++Tc8rv+H7mrK1zyJQOAwCFg8LZ3lTs1T5afGWeZrLPhPT9HLnIwSjCyJqPWVMIrMxbjcmBr2oQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.29.7.tgz", - "integrity": "sha512-RRnE2+eon1rJAq8MnoF1b5kTpY1vU88twHcvcKMrsqP/jxIRqDVs9iJB5fqPuqyeFAW0wJo4MlUIPpQCq/aRsg==", + "node_modules/@oxc-parser/binding-wasm32-wasi": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-wasm32-wasi/-/binding-wasm32-wasi-0.142.0.tgz", + "integrity": "sha512-3pLDyY3+oogW73RM5uehNgAiR/Xfb7fvO2Q1Z1gIqZ2+50XDVQmBVlRkHXZTU4gKnQHpwETNsYQVsJ3joVB2iA==", + "cpu": [ + "wasm32" + ], "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" + "@emnapi/core": "1.11.2", + "@emnapi/runtime": "1.11.2", + "@napi-rs/wasm-runtime": "^1.1.6" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-literals": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.29.7.tgz", - "integrity": "sha512-DZ/oLP21ZuWx1vKqnoNv6/tvEK48AQOBRai40CX9dTjGluvT/YZCyY3rryDtyUqCEoyNroy5KKPwX2iQCiRvyw==", + "node_modules/@oxc-parser/binding-win32-arm64-msvc": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-0.142.0.tgz", + "integrity": "sha512-Had/VeVY28Oyb0K+Q4FV8KCzoBycIh93oDK6pCbya9lkzdq+ikMHMgBubsdqqlybjJmQRawCQRrnBRHyQwYvcQ==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.29.7.tgz", - "integrity": "sha512-A0H91hh6W8MFRkp5TqJmMr39jzGD1A1E1Ysiv2O06Sfbhkapm+XyIzxWCEh5kqwOZ1/8QZ0dY3SeQ7XBqfJd5Q==", + "node_modules/@oxc-parser/binding-win32-ia32-msvc": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-ia32-msvc/-/binding-win32-ia32-msvc-0.142.0.tgz", + "integrity": "sha512-GGi3+YphVHavvgs6gum2UXoNCqzHAmPt/nXkn8ZQZstV2Q1qZD1Mn8fz/nWrDkefHQtrG/+1/XrbMxsBTo6Svw==", + "cpu": [ + "ia32" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.29.7.tgz", - "integrity": "sha512-hl1kwFZCCiDyfH25Xmco9jTrkPgnS9pmOzSG7W5I4SaGbLeqKv417hcU2RKmaxoPEgsoJh7ZPOrnPGq99bHoUg==", + "node_modules/@oxc-parser/binding-win32-x64-msvc": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-parser/binding-win32-x64-msvc/-/binding-win32-x64-msvc-0.142.0.tgz", + "integrity": "sha512-Ny/Wv4Us1LGC/ljwNTp+Hx3r/pH15EFfeDF0p+n898gt+TtRd6C9SccHcuUhDiNTb8s5tt7jdeAMDRQZ4Vq6hg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.29.7.tgz", - "integrity": "sha512-fxtQoH3m5ywUSIfaH0FGCzWu4McsYon5bD3K4XnskC7f+OyQMj7rsOMi4NvvmJ83WwBAg4UCe+ov4VZlqEvyew==", + "node_modules/@oxc-project/types": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.142.0.tgz", + "integrity": "sha512-7W+2q5AKQVU36fkaryontrHn3YDt1RyUYXatw9i5H8ocYe2sPKSFB6eS8WNPeRKiN1qAWWZUPm7gwFzJGrccqQ==", "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, - "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.29.7.tgz", - "integrity": "sha512-j0vCldybPC5b5dwCQOJ21uKtHzt7hxLygJTg9eF1ScfaikEDNfzn94XoW5Fi+seBR0nCyL23xaBFFkq7dTM8XQ==", + "node_modules/@oxc-resolver/binding-android-arm-eabi": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm-eabi/-/binding-android-arm-eabi-11.24.2.tgz", + "integrity": "sha512-y09e0L0SRI2OA2tUIrjBgoV3eH5hvUKXNkJqXmNo5V2WxIjyC7I7aJfRLMEVpA8yi95f90gFDvO0VMgrDw+vwA==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.29.7.tgz", - "integrity": "sha512-TM2ZcQLoG2/y4HODiStCo10DibYhWhGWAwVv+EQKmG/7GFl0N+AAmUiXOMKM+aiJ9XBJ9AHVZBvTzMnJ2sM3cQ==", + "node_modules/@oxc-resolver/binding-android-arm64": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-android-arm64/-/binding-android-arm64-11.24.2.tgz", + "integrity": "sha512-cl4icWaZFnLdg8m6qtnh5rBMuGbxc/ptStFHLeCNwr+2cZjkjNwQu/jYRS0CHlnPecOJMpuS5M6/BH+0J/YkEg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "optional": true, + "os": [ + "android" + ] }, - "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.29.7.tgz", - "integrity": "sha512-B4UkaTK3QpgCwJnrxKfMPKdo92CN7OKXAlpAAnM3UPu0Q0lCCk57ylA9AJbRy2v8dDKOPAAWcoR6CMyeoHwRCA==", + "node_modules/@oxc-resolver/binding-darwin-arm64": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-arm64/-/binding-darwin-arm64-11.24.2.tgz", + "integrity": "sha512-At29QEMF6HajbQvgY8K6OXnHD1x9rad74xBEfmCB6ZqCGsdq75aK7tOYcTbOanMy8qdIBrfL3SMr3p/lfSlb9w==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-module-transforms": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.29.7.tgz", - "integrity": "sha512-vuFoLwr4qnv2xbZ16SQd6uPcH5FNrLHhk/Jzo++0XJFcaDsr4gjJVg6j398oMHiC+83k/GiBzviwF5KBJkPUtQ==", + "node_modules/@oxc-resolver/binding-darwin-x64": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-darwin-x64/-/binding-darwin-x64-11.24.2.tgz", + "integrity": "sha512-A5Kqr1EUj4oIL5CF4WRssq/o5P0Y11cwoFouMRmQ7YnC/A8V93nv1nb7aSU8HwcgmXropjLNkVTl4MN87cu28Q==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@babel/plugin-transform-new-target": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.29.7.tgz", - "integrity": "sha512-fEo41GmsOUhOBlw8ioo6zvjX5Xc2Lqkzlyfqbpsk3eB6TReV18uhxZ0esfEokVbY2+PVJAQHNKxER6lGrzNd3A==", + "node_modules/@oxc-resolver/binding-freebsd-x64": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-freebsd-x64/-/binding-freebsd-x64-11.24.2.tgz", + "integrity": "sha512-R5xkRBRRz7ceH/P5Jrc6G7FmdUdgpLYyESFAUDVTNQ9K0sGPxcp4ljiwEwEqsvNcQ4sYbMRrWcHHBCu7ksAJVw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "optional": true, + "os": [ + "freebsd" + ] }, - "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.29.7.tgz", - "integrity": "sha512-idmp1dFaekP9GbcMvG24Kvw2BfhFZjHnNJCkV4WuIY4PskJzwI3f1N5OdgYke38T7rftO6ERulFRn2cFeZwRkg==", + "node_modules/@oxc-resolver/binding-linux-arm-gnueabihf": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-11.24.2.tgz", + "integrity": "sha512-k/RuYL4L/R58IBn3wT5ma3Wh4k62bp1eYCFRWCmMsasUOqL+H6sW0VGFadEzKWXFFlz+2uIMoeMk9ySSZJHgbg==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.29.7.tgz", - "integrity": "sha512-zR7fv/z14OjgHl4AgRtkDBvBMhIzCxqV/qN/2BCRC7LjFwvuzjYe7gDWxC4Wl/SNsLM6SE1IWvRPYMgSJaUvNw==", + "node_modules/@oxc-resolver/binding-linux-arm-musleabihf": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm-musleabihf/-/binding-linux-arm-musleabihf-11.24.2.tgz", + "integrity": "sha512-bnHAak3ujYfH5pKk4NieFNbvYvernfoQDgwLddbZ3OtMYrem87/qjlA+u+aKG0oZcqSLGCful/6/CEA+aeAgaA==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.29.7.tgz", - "integrity": "sha512-Ld98jn4c0smUywL57m7SgsHq3OpThOa6LqZJif3G6jYOovPleoFhVrBJ1WegRApSFB2wu4+RelAj9AC9G08Z4A==", + "node_modules/@oxc-resolver/binding-linux-arm64-gnu": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-11.24.2.tgz", + "integrity": "sha512-vDT3KHgzYp47gmtNOqL2VNhCyl5Zv643eyxm//A68J8DeUGXrvD1pZFiaT4jSfe+RInfnn1R2yVHye4enx6RnA==", + "cpu": [ + "arm64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/plugin-transform-destructuring": "^7.29.7", - "@babel/plugin-transform-parameters": "^7.29.7", - "@babel/traverse": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@babel/plugin-transform-object-super": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.29.7.tgz", - "integrity": "sha512-Ea/diGcw0twB5IlZPO5sgET6fJsLJqPABqTuFWIR+iMPGPZJkATEIWx0wa+aEQ5UY1CBQyP/gkAiLEqn1vBiQA==", + "node_modules/@oxc-resolver/binding-linux-arm64-musl": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-arm64-musl/-/binding-linux-arm64-musl-11.24.2.tgz", + "integrity": "sha512-+kMlQvbzfyEYtu5FcjE4p+ttBLpKW4d/AsAsuE69BxV6V4twZJeIQZFfD8gh/wqglY0MkPSezWXQH0jBV13MUw==", + "cpu": [ + "arm64" + ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-replace-supers": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.29.7.tgz", - "integrity": "sha512-sLsyndxK2VwX6yNUOakMb7Sh553ZTe/vVM1XJ+9Z5aW1ytsc8xOIwmyk05NNjN60vkc5/KqoTH6hB4V41LJhng==", + "node_modules/@oxc-resolver/binding-linux-ppc64-gnu": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-11.24.2.tgz", + "integrity": "sha512-shjfMhmZ3gq9fv/w7bi3PnZlgOPG+2QAOFf0BJF0EgBSIGZ6PMLN2zbGEblTUYB/NKVDRyYhE2ff3dJ1QqNPkA==", + "cpu": [ + "ppc64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.29.7.tgz", - "integrity": "sha512-6GM1dhvK3gNODkXcEcMCOLEDCLSoZ/sBbro2Ax8HURyasQ4NshagQixkRFdh5niI6E4gmA/jYI/4aT7rRos3ZQ==", + "node_modules/@oxc-resolver/binding-linux-riscv64-gnu": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-gnu/-/binding-linux-riscv64-gnu-11.24.2.tgz", + "integrity": "sha512-zGelwFR5oRo+b69k8Lrzun86DyUHzfKN6cnjbR9l7Z7NIRznOE/2ZvPa1IUKqAL2PzAXOdwkfVqNvO1H2RlpAw==", + "cpu": [ + "riscv64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@babel/plugin-transform-parameters": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.29.7.tgz", - "integrity": "sha512-ZDOBqV/qLYJI0YElr8DcENEyARsFQeESqWXH6gZlghYXuPPjvweuDhP4VyEi4BlUBlLRFZVjxoZDMjxhLW766g==", + "node_modules/@oxc-resolver/binding-linux-riscv64-musl": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-riscv64-musl/-/binding-linux-riscv64-musl-11.24.2.tgz", + "integrity": "sha512-qxZ1SWCXJY0eyhAlP6Lmo9F2Nrtx7EkYj9oCgL8apDPCwXwCEDA2U697bbT81JIc2IrVjxO4KX6WU2N+oN9Z4w==", + "cpu": [ + "riscv64" + ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.29.7.tgz", - "integrity": "sha512-/6Rz4DK1ETDEM/bWHsPHcaEe7ZaT1EqSXjtSP/L0DijOYuaUhiRiOKcwpZ8P7zR4xXEHc2ITdiCgBm9Tpyv9ug==", + "node_modules/@oxc-resolver/binding-linux-s390x-gnu": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-11.24.2.tgz", + "integrity": "sha512-sGCecF3cx2DFlH4t/z7ApnOnXqN48p5p5mlHDEnHTAukQa2P+qMVE4CwyWE9W+q/m3QJ7kKfGrIjax31f44oFQ==", + "cpu": [ + "s390x" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.29.7.tgz", - "integrity": "sha512-+BNo06dnrzdNNqCm1X6YUaVv0DKk8Q+JYcoZfOkLhYWNCXzlwTSRq8zGWayT1csjcpNXV9CQTBRRbmTLZac5cA==", - "dev": true, + "node_modules/@oxc-resolver/binding-linux-x64-gnu": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-gnu/-/binding-linux-x64-gnu-11.24.2.tgz", + "integrity": "sha512-k/VlMMcSzMlahb3/fENM4rTlsJ0s3fFROA0KXPBmKggqmTSaE383sl8F3KCOXPLmVsYfW6hCitMhXCEtNeZxxg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-create-class-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.29.7.tgz", - "integrity": "sha512-bOMRLQuI0A5ZqHq3OWJ89/rXpJ/NJrbVhXiP4zwPGMs6kpcVsuTUNjwoE30K0Qm3mf48a/TnRYYD6vPNqcg6jA==", + "node_modules/@oxc-resolver/binding-linux-x64-musl": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-linux-x64-musl/-/binding-linux-x64-musl-11.24.2.tgz", + "integrity": "sha512-8hbnZyNi97b/8wapYaIF9+t9GmZKBW2vunaOc3h9HGJptH7b7XpvZqOTBSm/MpTjr7H497BlgOaSfLUdhmy2bw==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "optional": true, + "os": [ + "linux" + ] }, - "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.29.7.tgz", - "integrity": "sha512-rNNFV0DBAJp988xW2DOntfDoYn1eR8GGF5AT5vYc+rjyfaQkM242c9tZUHHPe7KYaiJizXPWhQTzzdbXySyhBw==", + "node_modules/@oxc-resolver/binding-openharmony-arm64": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-openharmony-arm64/-/binding-openharmony-arm64-11.24.2.tgz", + "integrity": "sha512-MvyGik3a6pVgZ0t/kWlbmFxFLmXQJwgLsY2eYFHLpy0wGwRbfzeIGgDwQ3kXqE30z+kSXennRkCrT7TUvkptNg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "optional": true, + "os": [ + "openharmony" + ] }, - "node_modules/@babel/plugin-transform-regexp-modifiers": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regexp-modifiers/-/plugin-transform-regexp-modifiers-7.29.7.tgz", - "integrity": "sha512-mB5Fs0VWrJ42ZCmc8114v60qetdaUVNkj9PmSZRmanCZM3S9hm0CFRLjRmYIsuXav14l2jvZ+4T8iiCGnhj3nQ==", + "node_modules/@oxc-resolver/binding-wasm32-wasi": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-wasm32-wasi/-/binding-wasm32-wasi-11.24.2.tgz", + "integrity": "sha512-vHcssMPwO08RTvj/c0iOBz90attxyG3wQJ0dTcyEQK43LRpcdLWZlV5feBhv6Isn6ahbQIzHbCgfa81+RiML0Q==", + "cpu": [ + "wasm32" + ], "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" + "@emnapi/core": "1.11.2", + "@emnapi/runtime": "1.11.2", + "@napi-rs/wasm-runtime": "^1.1.6" }, "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" + "node": ">=14.0.0" } }, - "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.29.7.tgz", - "integrity": "sha512-5+YhdpVgmfSmwZyLMftfaiffLRMHjzIRHFHHLdibcSyJm2pasMrKHrO3Ptrt2DRshjvpgjEJJ1zVW14WPq/6QA==", + "node_modules/@oxc-resolver/binding-win32-arm64-msvc": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-11.24.2.tgz", + "integrity": "sha512-uokJqro2iBqkFvJdKQLP7d8/BUmFwESQFVmIJUQKj1Xn1a/LysJoe1vmeECLF5b3jsV8CAL5sEMJXX6SdK9Nhg==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.29.7.tgz", - "integrity": "sha512-I+WYbGBAiCn7nA6xBrlgPH+MB7HWb4u8pv5S0Pv7OtwNvIFvCCb24YlttKEeUFVurfBCEaOTnuhlqsb7f0Z5Dg==", + "node_modules/@oxc-resolver/binding-win32-x64-msvc": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/@oxc-resolver/binding-win32-x64-msvc/-/binding-win32-x64-msvc-11.24.2.tgz", + "integrity": "sha512-UqGPmo56KDfLlfXFAFIrNflHT8tFxWGEivWg3Zeyp4Uy2NlKN1FGPr6/BxcLGG3+kZ6Wp14g5Uj+n71boqZfiw==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/@babel/plugin-transform-spread": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.29.7.tgz", - "integrity": "sha512-/u5K1QWada7tbYNqTjMh96718g9NTwh9tfPJMsSmVsQwGT447FskV+KcfeXkXq2GWki4EM/MuTdmBec+hOuVTQ==", + "node_modules/@paralleldrive/cuid2": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", + "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "@noble/hashes": "^1.1.5" } }, - "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.29.7.tgz", - "integrity": "sha512-BCHzNYJGe9l7EpwwDBN/ztlL2NYFFq8hp9ddjtUEM9f2O7S7kKV/lL6Fwo7IF7NSkYhPK2vO+86nIGltA90MsA==", + "node_modules/@pinojs/redact": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz", + "integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==", + "license": "MIT" + }, + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.3.tgz", + "integrity": "sha512-zrJtHDcaZJ1Fp7xf4hNl+7seH9Cn/N5TwLYkhgXREtBwAd/jaqW3uqeHxpDugJLVICWg4eW44kOQEGJ1r6jCGw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "android" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.29.7.tgz", - "integrity": "sha512-NCSEJ4sLFU2gqAub45HYh4fus2yQ36rr6ei6vpU7NdoJqCpxvEG8E6eJpscGyXP3VHD2Ny+fSXr04k1hoUrFqA==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.3.tgz", + "integrity": "sha512-ieIiibVCp0tX7TLu2cafoNPv8wJyYi01ekXpbf8q2j7F4rGAhhXb/eQh7ge9DRBY78GwmRQtvjZDux7EDbA8kA==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.29.7.tgz", - "integrity": "sha512-223mNGoTkBiTEWFoK+Q6Go3tueMRclO8vxxxxquNCYuNI4jWOofFKJRRDu6SDrB8Sgo1UEGW9T4GAQ8ZyRso1A==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.3.tgz", + "integrity": "sha512-Zh9tCon19eDXJoihx0rqKhMUlMYqzwj3aPsSuHmI4RWZh62dWUL+DJN4C5YQya5TcQBJU/Fe8+rY0jhXTQITqA==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-typescript": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.29.7.tgz", - "integrity": "sha512-jK52h8LaLc7JarhQV2ofeFMts4H7vnOXnqZNA6fYglBTZewRBE51KWt3BUltW1P+KoPsYkHoJeXePuz4zo2LMw==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.3.tgz", + "integrity": "sha512-nGbJWewA1wrXXZiQhjAT5rhibGfns5ZNkDVqxsO6zJ3f3YvpoDNNmGMSbbhLuXKjNScaBJVOAboztAWVespQMg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.29.7", - "@babel/helper-create-class-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-skip-transparent-expression-wrappers": "^7.29.7", - "@babel/plugin-syntax-typescript": "^7.29.7" - }, + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.29.7.tgz", - "integrity": "sha512-jCfXxSjf94lf4E0hKE0AByxF6F3/pVFqRdUUNkDJhsY0m1ZKjnN6ZYyMeHNpzflxb/0q5b7t3p+BE+SLF1WOtA==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.3.tgz", + "integrity": "sha512-QNniJr5Kml0kDEB98jiDOJjXNroxIIi0IXIbdYzY26Xt1pVbeP62+KnoIZLwirOymX/0jDk/2gI/bNUv7A7OIw==", + "cpu": [ + "arm" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.29.7.tgz", - "integrity": "sha512-OgZ+zoAJgZLUCunsTRQ5LAjOywDv5zzZ2/hQ5aMw1pGXyY2rtE8/chXYUmu3AlVHKpm10KEdG9aMwbI/K76ZGw==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.3.tgz", + "integrity": "sha512-TkqEAcmmvH3I/q4114NB4RVt6241Dao48pF45uLcFGrwAaIn0iITgTAKP/dLjbN0R4buJjGb91+UHSoFmpgIWw==", + "cpu": [ + "arm64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.29.7.tgz", - "integrity": "sha512-7D/x/23/d/3VqZ0QA+LGbZMlGwZjztBygSWWWsfTPoQ1oQ6Q1P6Mr3d0kk42XabyUVw+fha3LqdRsFqeKqvCyA==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.3.tgz", + "integrity": "sha512-NHqjnxpsndf4MPymxteFAWHHfkTL8HjWh1KB7z23ofZ6QO2euONuxDXjat69dKZRALnGypg8k8SsK8vZJoXv1Q==", + "cpu": [ + "arm64" + ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.29.7.tgz", - "integrity": "sha512-BLOhLht9DOJwIxlmp91wHvkXv1lguuHS3/FwUO8HL1H0u8s4hR1gASVFyilu9iGtcTRYqjTZmlsFFeQletntEg==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.3.tgz", + "integrity": "sha512-6tbrbwfz5GB9DQ4Jwo6hy9v+vR31xZlvzZ6n5Xut6Hhx5PvrA9q/HsK8KMaYQp063iqZGXwNvZtYNLD7EM/x0w==", + "cpu": [ + "ppc64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "node_modules/@babel/preset-env": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.29.7.tgz", - "integrity": "sha512-GYzX36n1nsciIb0uyH0GHwxwtNwPQIcpxSeiVLDtG/B7jB5xXgchnmL1f/jCX5o+pwnaDBtO60ONSJhEBJfxYA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.29.7", - "@babel/helper-compilation-targets": "^7.29.7", - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.29.7", - "@babel/plugin-bugfix-safari-class-field-initializer-scope": "^7.29.7", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.29.7", - "@babel/plugin-bugfix-safari-rest-destructuring-rhs-array": "^7.29.7", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.29.7", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.29.7", - "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", - "@babel/plugin-syntax-import-assertions": "^7.29.7", - "@babel/plugin-syntax-import-attributes": "^7.29.7", - "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.29.7", - "@babel/plugin-transform-async-generator-functions": "^7.29.7", - "@babel/plugin-transform-async-to-generator": "^7.29.7", - "@babel/plugin-transform-block-scoped-functions": "^7.29.7", - "@babel/plugin-transform-block-scoping": "^7.29.7", - "@babel/plugin-transform-class-properties": "^7.29.7", - "@babel/plugin-transform-class-static-block": "^7.29.7", - "@babel/plugin-transform-classes": "^7.29.7", - "@babel/plugin-transform-computed-properties": "^7.29.7", - "@babel/plugin-transform-destructuring": "^7.29.7", - "@babel/plugin-transform-dotall-regex": "^7.29.7", - "@babel/plugin-transform-duplicate-keys": "^7.29.7", - "@babel/plugin-transform-duplicate-named-capturing-groups-regex": "^7.29.7", - "@babel/plugin-transform-dynamic-import": "^7.29.7", - "@babel/plugin-transform-explicit-resource-management": "^7.29.7", - "@babel/plugin-transform-exponentiation-operator": "^7.29.7", - "@babel/plugin-transform-export-namespace-from": "^7.29.7", - "@babel/plugin-transform-for-of": "^7.29.7", - "@babel/plugin-transform-function-name": "^7.29.7", - "@babel/plugin-transform-json-strings": "^7.29.7", - "@babel/plugin-transform-literals": "^7.29.7", - "@babel/plugin-transform-logical-assignment-operators": "^7.29.7", - "@babel/plugin-transform-member-expression-literals": "^7.29.7", - "@babel/plugin-transform-modules-amd": "^7.29.7", - "@babel/plugin-transform-modules-commonjs": "^7.29.7", - "@babel/plugin-transform-modules-systemjs": "^7.29.7", - "@babel/plugin-transform-modules-umd": "^7.29.7", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.29.7", - "@babel/plugin-transform-new-target": "^7.29.7", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.29.7", - "@babel/plugin-transform-numeric-separator": "^7.29.7", - "@babel/plugin-transform-object-rest-spread": "^7.29.7", - "@babel/plugin-transform-object-super": "^7.29.7", - "@babel/plugin-transform-optional-catch-binding": "^7.29.7", - "@babel/plugin-transform-optional-chaining": "^7.29.7", - "@babel/plugin-transform-parameters": "^7.29.7", - "@babel/plugin-transform-private-methods": "^7.29.7", - "@babel/plugin-transform-private-property-in-object": "^7.29.7", - "@babel/plugin-transform-property-literals": "^7.29.7", - "@babel/plugin-transform-regenerator": "^7.29.7", - "@babel/plugin-transform-regexp-modifiers": "^7.29.7", - "@babel/plugin-transform-reserved-words": "^7.29.7", - "@babel/plugin-transform-shorthand-properties": "^7.29.7", - "@babel/plugin-transform-spread": "^7.29.7", - "@babel/plugin-transform-sticky-regex": "^7.29.7", - "@babel/plugin-transform-template-literals": "^7.29.7", - "@babel/plugin-transform-typeof-symbol": "^7.29.7", - "@babel/plugin-transform-unicode-escapes": "^7.29.7", - "@babel/plugin-transform-unicode-property-regex": "^7.29.7", - "@babel/plugin-transform-unicode-regex": "^7.29.7", - "@babel/plugin-transform-unicode-sets-regex": "^7.29.7", - "@babel/preset-modules": "0.1.6-no-external-plugins", - "babel-plugin-polyfill-corejs2": "^0.4.15", - "babel-plugin-polyfill-corejs3": "^0.14.0", - "babel-plugin-polyfill-regenerator": "^0.6.6", - "core-js-compat": "^3.48.0", - "semver": "^6.3.1" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/preset-modules": { - "version": "0.1.6-no-external-plugins", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.3.tgz", + "integrity": "sha512-oyuXxXmoZHjXC917IAPFAAv4wWAa0cM9afk8nx1+9/jNNOX1uPf8yDA6p7G0RypOfw/X0PQt5IfoquY1um+zSg==", + "cpu": [ + "s390x" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@babel/types": "^7.4.4", - "esutils": "^2.0.2" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/preset-typescript": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.29.7.tgz", - "integrity": "sha512-/Foi8vKY2EVbed/1eZx0gJEEwHAIxogrySI7rULcRIvhZzbvoE/b5qG5Ghc0WKAFKOHA9SD1x7RsFlOYdutIiQ==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.3.tgz", + "integrity": "sha512-TytMwF2KVGqP2tgd0I1OY0PAv78dZRAYcF5ssDzjM34SUXCED3uXvSd5+lHoC0bTD6eEdFz7LdQNCO1y0oVk9w==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.29.7", - "@babel/helper-validator-option": "^7.29.7", - "@babel/plugin-syntax-jsx": "^7.29.7", - "@babel/plugin-transform-modules-commonjs": "^7.29.7", - "@babel/plugin-transform-typescript": "^7.29.7" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/register": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.29.7.tgz", - "integrity": "sha512-AMGJoWuES861riy6pcB0fphE1YXybtQnBYQMuIyPv6mKLiosfa79BKTnAOyx215c/3RJPJpdQwoHZ3earVH7AA==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.3.tgz", + "integrity": "sha512-/E9m3qstrJFVPoULV25mVQblSNExY2+kBsYe4sy0Tn0yOOgJ8wZbZt3KnRbF/XeU2Gl1STKUQnDNTqhIE5MD4A==", + "cpu": [ + "x64" + ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", - "dependencies": { - "clone-deep": "^4.0.1", - "find-cache-dir": "^2.0.0", - "make-dir": "^2.1.0", - "pirates": "^4.0.6", - "source-map-support": "^0.5.16" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/template": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", - "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.3.tgz", + "integrity": "sha512-Kr0OcsoQI816i6HOl3vFHpd1K0eZyh76zgfj4c1nTyaTsd5r2Mj1lwM4R90y/qaCfmTn9eHy0SKwi98eitRxug==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/types": "^7.29.7" - }, + "optional": true, + "os": [ + "openharmony" + ], "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/traverse": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", - "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.3.tgz", + "integrity": "sha512-hOtMwTqnME+/gJcH/PCZ0wn0zPUjiWOgkHpxbSJpfGKMezHltx1S7/k1SitzVa7Ww2cqrDDaFbZEhcJZO8o+Jw==", + "cpu": [ + "arm64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.7", - "@babel/generator": "^7.29.7", - "@babel/helper-globals": "^7.29.7", - "@babel/parser": "^7.29.7", - "@babel/template": "^7.29.7", - "@babel/types": "^7.29.7", - "debug": "^4.3.1" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@babel/types": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", - "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.3.tgz", + "integrity": "sha512-ekcqMMkI2PlhYnfzQnB/cEdYUVVJViWvoUyLrbzgDoi3Snfc1mVBwdnc306ufA5ejy8JSPjT2RlW1nQSjW7efg==", + "cpu": [ + "x64" + ], "dev": true, "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.29.7", - "@babel/helper-validator-identifier": "^7.29.7" - }, + "optional": true, + "os": [ + "win32" + ], "engines": { - "node": ">=6.9.0" + "node": "^20.19.0 || >=22.12.0" } }, - "node_modules/@bcoe/v8-coverage": { - "version": "1.0.2", + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", "dev": true, + "license": "MIT" + }, + "node_modules/@serialport/binding-mock": { + "version": "10.2.2", + "resolved": "https://registry.npmjs.org/@serialport/binding-mock/-/binding-mock-10.2.2.tgz", + "integrity": "sha512-HAFzGhk9OuFMpuor7aT5G1ChPgn5qSsklTFOTUX72Rl6p0xwcSVsRtG/xaGp6bxpN7fI9D/S8THLBWbBgS6ldw==", "license": "MIT", + "dependencies": { + "@serialport/bindings-interface": "^1.2.1", + "debug": "^4.3.3" + }, "engines": { - "node": ">=18" + "node": ">=12.0.0" } }, - "node_modules/@emnapi/core": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", - "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", - "dev": true, + "node_modules/@serialport/bindings-cpp": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-13.0.0.tgz", + "integrity": "sha512-r25o4Bk/vaO1LyUfY/ulR6hCg/aWiN6Wo2ljVlb4Pj5bqWGcSRC4Vse4a9AcapuAu/FeBzHCbKMvRQeCuKjzIQ==", + "hasInstallScript": true, "license": "MIT", - "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.2.2", - "tslib": "^2.4.0" + "@serialport/bindings-interface": "1.2.2", + "@serialport/parser-readline": "12.0.0", + "debug": "4.4.0", + "node-addon-api": "8.3.0", + "node-gyp-build": "4.8.4" + }, + "engines": { + "node": ">=18.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@emnapi/runtime": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", - "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", - "dev": true, + "node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-delimiter": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-12.0.0.tgz", + "integrity": "sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==", "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@emnapi/wasi-threads": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", - "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", - "dev": true, + "node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-readline": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-12.0.0.tgz", + "integrity": "sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==", "license": "MIT", - "optional": true, "dependencies": { - "tslib": "^2.4.0" + "@serialport/parser-delimiter": "12.0.0" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@es-joy/jsdoccomment": { - "version": "0.86.0", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.86.0.tgz", - "integrity": "sha512-ukZmRQ81WiTpDWO6D/cTBM7XbrNtutHKvAVnZN/8pldAwLoJArGOvkNyxPTBGsPjsoaQBJxlH+tE2TNA/92Qgw==", - "dev": true, + "node_modules/@serialport/bindings-cpp/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", "dependencies": { - "@types/estree": "^1.0.8", - "@typescript-eslint/types": "^8.58.0", - "comment-parser": "1.4.6", - "esquery": "^1.7.0", - "jsdoc-type-pratt-parser": "~7.2.0" + "ms": "^2.1.3" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@es-joy/resolve.exports": { - "version": "1.2.0", - "dev": true, + "node_modules/@serialport/bindings-cpp/node_modules/node-addon-api": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.0.tgz", + "integrity": "sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==", "license": "MIT", "engines": { - "node": ">=10" + "node": "^18 || ^20 || >= 21" } }, - "node_modules/@eslint-community/eslint-utils": { - "version": "4.9.1", - "dev": true, + "node_modules/@serialport/bindings-interface": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@serialport/bindings-interface/-/bindings-interface-1.2.2.tgz", + "integrity": "sha512-CJaUd5bLvtM9c5dmO9rPBHPXTa9R2UwpkJ0wdh9JCYcbrPWsKz+ErvR0hBLeo7NPeiFdjFO4sonRljiw4d2XiA==", "license": "MIT", - "dependencies": { - "eslint-visitor-keys": "^3.4.3" - }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^12.22 || ^14.13 || >=16" + } + }, + "node_modules/@serialport/parser-byte-length": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-13.0.0.tgz", + "integrity": "sha512-32yvqeTAqJzAEtX5zCrN1Mej56GJ5h/cVFsCDPbF9S1ZSC9FWjOqNAgtByseHfFTSTs/4ZBQZZcZBpolt8sUng==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" }, "funding": { - "url": "https://opencollective.com/eslint" - }, - "peerDependencies": { - "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "dev": true, - "license": "Apache-2.0", + "node_modules/@serialport/parser-cctalk": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-13.0.0.tgz", + "integrity": "sha512-RErAe57g9gvnlieVYGIn1xymb1bzNXb2QtUQd14FpmbQQYlcrmuRnJwKa1BgTCujoCkhtaTtgHlbBWOxm8U2uA==", + "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=20.0.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@eslint-community/regexpp": { - "version": "4.12.2", - "dev": true, + "node_modules/@serialport/parser-delimiter": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-13.0.0.tgz", + "integrity": "sha512-Qqyb0FX1avs3XabQqNaZSivyVbl/yl0jywImp7ePvfZKLwx7jBZjvL+Hawt9wIG6tfq6zbFM24vzCCK7REMUig==", "license": "MIT", "engines": { - "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@eslint/config-array": { - "version": "0.23.5", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.23.5.tgz", - "integrity": "sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^3.0.5", - "debug": "^4.3.1", - "minimatch": "^10.2.4" + "node_modules/@serialport/parser-inter-byte-timeout": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-13.0.0.tgz", + "integrity": "sha512-a0w0WecTW7bD2YHWrpTz1uyiWA2fDNym0kjmPeNSwZ2XCP+JbirZt31l43m2ey6qXItTYVuQBthm75sPVeHnGA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" }, + "funding": { + "url": "https://opencollective.com/serialport/donate" + } + }, + "node_modules/@serialport/parser-packet-length": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-13.0.0.tgz", + "integrity": "sha512-60ZDDIqYRi0Xs2SPZUo4Jr5LLIjtb+rvzPKMJCohrO6tAqSDponcNpcB1O4W21mKTxYjqInSz+eMrtk0LLfZIg==", + "license": "MIT", "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": ">=8.6.0" } }, - "node_modules/@eslint/config-helpers": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.6.0.tgz", - "integrity": "sha512-ii6Bw9jJ2zi2cWA2Z+9/QZ/+3DX6kwaV5Q986D/CdP3Lap3w/pgQZ373FV7byY/i7L4IRH/G43I5dz1ClsCbpA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@serialport/parser-readline": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-13.0.0.tgz", + "integrity": "sha512-dov3zYoyf0dt1Sudd1q42VVYQ4WlliF0MYvAMA3MOyiU1IeG4hl0J6buBA2w4gl3DOCC05tGgLDN/3yIL81gsA==", + "license": "MIT", "dependencies": { - "@eslint/core": "^1.2.1" + "@serialport/parser-delimiter": "13.0.0" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/core": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-1.2.1.tgz", - "integrity": "sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.15" + "node": ">=20.0.0" }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "funding": { + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@eslint/js": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-10.0.1.tgz", - "integrity": "sha512-zeR9k5pd4gxjZ0abRoIaxdc7I3nDktoXZk2qOv9gCNWx3mVwEn32VRhyLaRsDiJjTs0xq/T8mfPtyuXu7GWBcA==", - "dev": true, + "node_modules/@serialport/parser-ready": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-13.0.0.tgz", + "integrity": "sha512-JNUQA+y2Rfs4bU+cGYNqOPnNMAcayhhW+XJZihSLQXOHcZsFnOa2F9YtMg9VXRWIcnHldHYtisp62Etjlw24bw==", "license": "MIT", "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": ">=20.0.0" }, "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "eslint": "^10.0.0" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@eslint/object-schema": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", - "integrity": "sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==", - "dev": true, - "license": "Apache-2.0", + "node_modules/@serialport/parser-regex": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-13.0.0.tgz", + "integrity": "sha512-m7HpIf56G5XcuDdA3DB34Z0pJiwxNRakThEHjSa4mG05OnWYv0IG8l2oUyYfuGMowQWaVnQ+8r+brlPxGVH+eA==", + "license": "MIT", "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - } - }, - "node_modules/@eslint/plugin-kit": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.7.2.tgz", - "integrity": "sha512-+CNAzxglkrpNf/kKywqQfk74QjtceuOE7Qm+AF8miRvPF/wmmK5+OJOgVh3AVTT3RP2mH3+FOaxlE5v72owk0A==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@eslint/core": "^1.2.1", - "levn": "^0.4.1" + "node": ">=20.0.0" }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "funding": { + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@humanfs/core": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.2.tgz", - "integrity": "sha512-UhXNm+CFMWcbChXywFwkmhqjs3PRCmcSa/hfBgLIb7oQ5HNb1wS0icWsGtSAUNgefHeI+eBrA8I1fxmbHsGdvA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/types": "^0.15.0" - }, + "node_modules/@serialport/parser-slip-encoder": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-13.0.0.tgz", + "integrity": "sha512-fUHZEExm6izJ7rg0A1yjXwu4sOzeBkPAjDZPfb+XQoqgtKAk+s+HfICiYn7N2QU9gyaeCO8VKgWwi+b/DowYOg==", + "license": "MIT", "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/node": { - "version": "0.16.8", - "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.8.tgz", - "integrity": "sha512-gE1eQNZ3R++kTzFUpdGlpmy8kDZD/MLyHqDwqjkVQI0JMdI1D51sy1H958PNXYkM2rAac7e5/CnIKZrHtPh3BQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@humanfs/core": "^0.19.2", - "@humanfs/types": "^0.15.0", - "@humanwhocodes/retry": "^0.4.0" + "node": ">=20.0.0" }, - "engines": { - "node": ">=18.18.0" - } - }, - "node_modules/@humanfs/types": { - "version": "0.15.0", - "resolved": "https://registry.npmjs.org/@humanfs/types/-/types-0.15.0.tgz", - "integrity": "sha512-ZZ1w0aoQkwuUuC7Yf+7sdeaNfqQiiLcSRbfI08oAxqLtpXQr9AIVX7Ay7HLDuiLYAaFPu8oBYNq/QIi9URHJ3Q==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": ">=18.18.0" + "funding": { + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@humanwhocodes/module-importer": { - "version": "1.0.1", - "dev": true, - "license": "Apache-2.0", + "node_modules/@serialport/parser-spacepacket": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-13.0.0.tgz", + "integrity": "sha512-DoXJ3mFYmyD8X/8931agJvrBPxqTaYDsPoly9/cwQSeh/q4EjQND9ySXBxpWz5WcpyCU4jOuusqCSAPsbB30Eg==", + "license": "MIT", "engines": { - "node": ">=12.22" + "node": ">=20.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.4.3", - "dev": true, - "license": "Apache-2.0", + "node_modules/@serialport/stream": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-13.0.0.tgz", + "integrity": "sha512-F7xLJKsjGo2WuEWMSEO1SimRcOA+WtWICsY13r0ahx8s2SecPQH06338g28OT7cW7uRXI7oEQAk62qh5gHJW3g==", + "license": "MIT", + "dependencies": { + "@serialport/bindings-interface": "1.2.2", + "debug": "4.4.0" + }, "engines": { - "node": ">=18.18" + "node": ">=20.0.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@jridgewell/gen-mapping": { - "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "node_modules/@serialport/stream/node_modules/debug": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.0", - "@jridgewell/trace-mapping": "^0.3.24" + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@jridgewell/remapping": { - "version": "2.3.5", + "node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "license": "MIT" + }, + "node_modules/@sindresorhus/base62": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/base62/-/base62-1.0.0.tgz", + "integrity": "sha512-TeheYy0ILzBEI/CO55CP6zJCSdSWeRtGnHy8U8dWSUH4I68iqTsy7HkMktR4xakThc9jotkPQUXT4ITdbV7cHA==", "dev": true, "license": "MIT", - "dependencies": { - "@jridgewell/gen-mapping": "^0.3.5", - "@jridgewell/trace-mapping": "^0.3.24" + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@jridgewell/resolve-uri": { + "node_modules/@socket.io/component-emitter": { "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", - "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } + "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", + "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", + "license": "MIT" }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", - "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "dev": true, "license": "MIT" }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "node_modules/@stoprocent/bluetooth-hci-socket": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@stoprocent/bluetooth-hci-socket/-/bluetooth-hci-socket-2.3.0.tgz", + "integrity": "sha512-xTigfmWCBqNJyPibOCS8F4dTt0LdE5zHAlpOc919ladh1Q+PjQgSGMQZoXxpK9CzZgZCmPF5f90jC0IkogwQEg==", + "hasInstallScript": true, "license": "MIT", + "optional": true, + "os": [ + "linux", + "android", + "freebsd", + "win32", + "darwin" + ], "dependencies": { - "@jridgewell/resolve-uri": "^3.1.0", - "@jridgewell/sourcemap-codec": "^1.4.14" + "async": "^3.2.6", + "debug": "^4.3.7", + "node-addon-api": "^8.3.1", + "node-gyp-build": "^4.8.4", + "patch-package": "^8.0.0", + "serialport": "^12.0.0" + }, + "optionalDependencies": { + "usb": "^2.14.0" } }, - "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", - "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", - "dev": true, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/bindings-cpp": { + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-12.0.1.tgz", + "integrity": "sha512-r2XOwY2dDvbW7dKqSPIk2gzsr6M6Qpe9+/Ngs94fNaNlcTRCV02PfaoDmRgcubpNVVcLATlxSxPTIDw12dbKOg==", + "hasInstallScript": true, "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.3" + "@serialport/bindings-interface": "1.2.2", + "@serialport/parser-readline": "11.0.0", + "debug": "4.3.4", + "node-addon-api": "7.0.0", + "node-gyp-build": "4.6.0" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" + "engines": { + "node": ">=16.0.0" }, - "peerDependencies": { - "@emnapi/core": "^1.7.1", - "@emnapi/runtime": "^1.7.1" + "funding": { + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@noble/hashes": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", - "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", - "dev": true, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-delimiter": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-11.0.0.tgz", + "integrity": "sha512-aZLJhlRTjSmEwllLG7S4J8s8ctRAS0cbvCpO87smLvl3e4BgzbVgF6Z6zaJd3Aji2uSiYgfedCdNc4L6W+1E2g==", "license": "MIT", + "optional": true, "engines": { - "node": "^14.21.3 || >=16" + "node": ">=12.0.0" }, "funding": { - "url": "https://paulmillr.com/funding/" + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@node-usb/usb-darwin-arm64": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@node-usb/usb-darwin-arm64/-/usb-darwin-arm64-3.0.0.tgz", - "integrity": "sha512-wBnK74ToiFpH8QGsOSkxjx2BllHIaeW57AUIrAEd1ZWdulbR7l63e07efUSi+iQDf7Qd8HLkD1DnA4w92lagcQ==", - "cpu": [ - "arm64" - ], + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-readline": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-11.0.0.tgz", + "integrity": "sha512-rRAivhRkT3YO28WjmmG4FQX6L+KMb5/ikhyylRfzWPw0nSXy97+u07peS9CbHqaNvJkMhH1locp2H36aGMOEIA==", "license": "MIT", "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@serialport/parser-delimiter": "11.0.0" + }, "engines": { - "node": ">= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@node-usb/usb-darwin-x64": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@node-usb/usb-darwin-x64/-/usb-darwin-x64-3.0.0.tgz", - "integrity": "sha512-myCWHDMSO7Xo6bSmCprTAuIj/KBD+z2YpKVnDCv65ZB5vF83gqz+7Qlnbqgr92IarAjPLldkyM5dgpHxGF9l5A==", - "cpu": [ - "x64" - ], + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/bindings-cpp/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "license": "MIT", "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "ms": "2.1.2" + }, "engines": { - "node": ">= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/@node-usb/usb-linux-arm-gnueabihf": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@node-usb/usb-linux-arm-gnueabihf/-/usb-linux-arm-gnueabihf-3.0.0.tgz", - "integrity": "sha512-EOQOP7QQf92Nr37jaBlL7W4pkB+9YCVxI/l1PZ19KZr/kqBIArdVw4DbkADmJmLwYl4yIeDsJLuQ2O9xLbYcvA==", - "cpu": [ - "arm" - ], + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/bindings-cpp/node_modules/node-addon-api": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz", + "integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==", "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0" - } + "optional": true }, - "node_modules/@node-usb/usb-linux-arm64-gnu": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@node-usb/usb-linux-arm64-gnu/-/usb-linux-arm64-gnu-3.0.0.tgz", - "integrity": "sha512-H6TLWmgeK0QSDUUCR1KRGQiNWFnSsv9Ec2Bnet8LQJrRtdb0np92oObgzn7X7nz4rpcuahfxH8H2xnLQatZnWA==", - "cpu": [ - "arm64" - ], - "libc": [ - "glibc" - ], + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/bindings-cpp/node_modules/node-gyp-build": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", + "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", "license": "MIT", "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0" + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" } }, - "node_modules/@node-usb/usb-linux-arm64-musl": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@node-usb/usb-linux-arm64-musl/-/usb-linux-arm64-musl-3.0.0.tgz", - "integrity": "sha512-d2ROSTlGh0axwSVw3cGXlUEzCW8vJsJOI6T/82xsB/mO+Wy8IC6eRaVzIyEx+hrBiW/TNVI8d9QrIHWG8I9/5A==", - "cpu": [ - "arm64" - ], - "libc": [ - "musl" - ], + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-byte-length": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-12.0.0.tgz", + "integrity": "sha512-0ei0txFAj+s6FTiCJFBJ1T2hpKkX8Md0Pu6dqMrYoirjPskDLJRgZGLqoy3/lnU1bkvHpnJO+9oJ3PB9v8rNlg==", "license": "MIT", "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@node-usb/usb-linux-x64-gnu": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@node-usb/usb-linux-x64-gnu/-/usb-linux-x64-gnu-3.0.0.tgz", - "integrity": "sha512-r3I6kGKiIm6J5qpdte4aHvMXXizLPonwq5f2ZGZGBY9VJUrUVjV5g1wW5spPZoq5E+R1V+metup1zMWd1Topew==", - "cpu": [ - "x64" - ], - "libc": [ - "glibc" - ], + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-cctalk": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-12.0.0.tgz", + "integrity": "sha512-0PfLzO9t2X5ufKuBO34DQKLXrCCqS9xz2D0pfuaLNeTkyGUBv426zxoMf3rsMRodDOZNbFblu3Ae84MOQXjnZw==", "license": "MIT", "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@node-usb/usb-linux-x64-musl": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@node-usb/usb-linux-x64-musl/-/usb-linux-x64-musl-3.0.0.tgz", - "integrity": "sha512-Qtzms0RQ4XXsuVwiNSDxXsuubfbzVELB4WH4MyP1ABigibA+yslMLHJLZbZyZA1NpOP32Pikmz6C7o9Ho6Z2LQ==", - "cpu": [ - "x64" - ], - "libc": [ - "musl" - ], + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-delimiter": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-12.0.0.tgz", + "integrity": "sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==", "license": "MIT", "optional": true, - "os": [ - "linux" - ], "engines": { - "node": ">= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@node-usb/usb-win32-arm64-msvc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@node-usb/usb-win32-arm64-msvc/-/usb-win32-arm64-msvc-3.0.0.tgz", - "integrity": "sha512-w4WfwMY07MPHF+393sCUTLl8cZMqyUlEvsxVZjDefXe3LsC/EKhxLWYqVOFszlhYGDpe5YLs2VjFLi5miq0ZjQ==", - "cpu": [ - "arm64" - ], + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-inter-byte-timeout": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-12.0.0.tgz", + "integrity": "sha512-GnCh8K0NAESfhCuXAt+FfBRz1Cf9CzIgXfp7SdMgXwrtuUnCC/yuRTUFWRvuzhYKoAo1TL0hhUo77SFHUH1T/w==", "license": "MIT", "optional": true, - "os": [ - "win32" - ], "engines": { - "node": ">= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@node-usb/usb-win32-ia32-msvc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@node-usb/usb-win32-ia32-msvc/-/usb-win32-ia32-msvc-3.0.0.tgz", - "integrity": "sha512-2531uD4hB1ccesjyapYB//VDEARnhx8u1oljBDy5O7o7qZh74KjNtlYXBZBAmCjtZfqIEH+L+YaBFVg39VdTUQ==", - "cpu": [ - "ia32" - ], + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-packet-length": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-12.0.0.tgz", + "integrity": "sha512-p1hiCRqvGHHLCN/8ZiPUY/G0zrxd7gtZs251n+cfNTn+87rwcdUeu9Dps3Aadx30/sOGGFL6brIRGK4l/t7MuQ==", "license": "MIT", "optional": true, - "os": [ - "win32" - ], "engines": { - "node": ">= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0" + "node": ">=8.6.0" } }, - "node_modules/@node-usb/usb-win32-x64-msvc": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@node-usb/usb-win32-x64-msvc/-/usb-win32-x64-msvc-3.0.0.tgz", - "integrity": "sha512-8wbRBW1WKzmOWyTJs9n7/rYytYjL+/hvxaW6rlYuHcj0gLDupDJb9l64FhPI8ZZtF19+LVatQeoNtQLoKVRAuQ==", - "cpu": [ - "x64" - ], + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-readline": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-12.0.0.tgz", + "integrity": "sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==", "license": "MIT", "optional": true, - "os": [ - "win32" - ], + "dependencies": { + "@serialport/parser-delimiter": "12.0.0" + }, "engines": { - "node": ">= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0" - } - }, - "node_modules/@oxc-project/types": { - "version": "0.137.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.137.0.tgz", - "integrity": "sha512-WT+Gb24i8hmvo85AIv2oEYouEXkRlKAlT9WaCa3TfLgNCN+GhrJOGZuIlMouAh38Qe4QOx26eUOVsq70qXrywA==", - "dev": true, - "license": "MIT", + "node": ">=12.0.0" + }, "funding": { - "url": "https://github.com/sponsors/Boshen" + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@paralleldrive/cuid2": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", - "integrity": "sha512-XO7cAxhnTZl0Yggq6jOgjiOHhbgcO4NqFqwSmQpjK3b6TEE6Uj/jfSk6wzYyemh3+I0sHirKSetjQwn5cZktFw==", - "dev": true, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-ready": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-12.0.0.tgz", + "integrity": "sha512-ygDwj3O4SDpZlbrRUraoXIoIqb8sM7aMKryGjYTIF0JRnKeB1ys8+wIp0RFMdFbO62YriUDextHB5Um5cKFSWg==", "license": "MIT", - "dependencies": { - "@noble/hashes": "^1.1.5" + "optional": true, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@pinojs/redact": { - "version": "0.4.0", - "license": "MIT" - }, - "node_modules/@rolldown/binding-android-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.3.tgz", - "integrity": "sha512-DT6Z3PhvioeHMvxo+xHc3KtqggrI7CCTXCmC2h/5zUlp5jVitv7XEy+9q5/7v8IolhlioawpMo8Kg0EEBy7J0g==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-regex": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-12.0.0.tgz", + "integrity": "sha512-dCAVh4P/pZrLcPv9NJ2mvPRBg64L5jXuiRxIlyxxdZGH4WubwXVXY/kBTihQmiAMPxbT3yshSX8f2+feqWsxqA==", "license": "MIT", "optional": true, - "os": [ - "android" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.3.tgz", - "integrity": "sha512-0NwgwsjM7LrsuVnXMK3koTpagBNOhloc/BNjKqZjv4V5zI5r13qx69uVhRx+o5Z0yy4Hzq+lpy7TAgUG/ocvrw==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-slip-encoder": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-12.0.0.tgz", + "integrity": "sha512-0APxDGR9YvJXTRfY+uRGhzOhTpU5akSH183RUcwzN7QXh8/1jwFsFLCu0grmAUfi+fItCkR+Xr1TcNJLR13VNA==", "license": "MIT", "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.3.tgz", - "integrity": "sha512-YtiBp4disu6V560loT6PjMdiRaWmVvDNrUunAalbiFx2ggeJwxdAsgZMcoGP17uyAsTwAj5V1niksxlHnVQ1Sw==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-spacepacket": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-12.0.0.tgz", + "integrity": "sha512-dozONxhPC/78pntuxpz/NOtVps8qIc/UZzdc/LuPvVsqCoJXiRxOg6ZtCP/W58iibJDKPZPAWPGYeZt9DJxI+Q==", "license": "MIT", "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^20.19.0 || >=22.12.0" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.3.tgz", - "integrity": "sha512-yD3EkEdXk2LypPxnf/kSZHirarsI8gcPzc62SukhR9VJTyvV+F9Q/GxWNuCojc7sXyuVC4DxRGhdDK4X8VSsbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.3.tgz", - "integrity": "sha512-c+8vieQbsD7HNAHKIA34w0GJ9FedFFuJGD+7E6vz7Q3uqAIugL5p45fhlsj4UaAsHpcmlqugBWMhA0/j7o0sIg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.3.tgz", - "integrity": "sha512-50jD0uUwLvur7Zz9LHz17kaAdTPjn5wN93hEgjvmYFRZwiR7ZJYovTd5ipyWJDAnXKvZ+wgc+/Ika6dwSF5OcA==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.3.tgz", - "integrity": "sha512-BO9+oPL8K9poZJBfYPsXNtYjPE5uM3qeehT3aFcW4LITOl+iSqhp0abzjR2nWBUNjIZeKXjAEWBZ64WjNoHd6w==", - "cpu": [ - "arm64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.3.tgz", - "integrity": "sha512-f3VpLB1vQ0Eo6ecr/6cekLnvYMFF4YBFoVGkfkvPLq1bAkbAwHYQPZKoAmG6OJyTcxxoC+AvezGx/S1obNC0Mw==", - "cpu": [ - "ppc64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.3.tgz", - "integrity": "sha512-AmurZ26Pqx/RI9N1gzEOCklkKXl927yjfXWUUS0O7Puh8ARM/Ob8qfrD3qnWksScdw6cSrW5PSHE9DyLu7+PtA==", - "cpu": [ - "s390x" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.3.tgz", - "integrity": "sha512-JJpqs8bRGITDOdbkNKnlojzBabbOHrqjSvDr0IVsZObE1lBcPjxItUEY9eWIDbxaJ3cGrXPWGfGkIxFijg/URg==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "glibc" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.3.tgz", - "integrity": "sha512-rSJcdjPxzA/by/6/rYs+v+bXU7UjvnbUWz8MJb6kh6+knqB1dCrtHg0uu7C/4haqJvqdkYHQ5IGn+tCH9GLW/g==", - "cpu": [ - "x64" - ], - "dev": true, - "libc": [ - "musl" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.3.tgz", - "integrity": "sha512-hQ3/PYkDJICgevvyNcVrihVeqq7k1Pp3VZ9lY+dauAYUJKO+auqApvANhvR1An9BhmqYKvW2Mu1F9u4DXSMLxQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.3.tgz", - "integrity": "sha512-Elcv/BtML9lXrV6JuKITc/grN2kYV9gjsQpW8Jfw4ioK0TOkjBjye0nnyqQNy9STNaI20lXNaQBRrD5gSgR0Yg==", - "cpu": [ - "wasm32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@emnapi/core": "1.11.1", - "@emnapi/runtime": "1.11.1", - "@napi-rs/wasm-runtime": "^1.1.6" - }, - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.3.tgz", - "integrity": "sha512-2DrEfhluH9yhiaFApmsjsjwrSYbNcY1oFTzYSP1a535jDbV98zCFanA/96TBUd0iDFcxGmw9QRExwGCXz3U+/g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.3.tgz", - "integrity": "sha512-OL4OMk7UPXOeVGGd3qo5zJyPIljf4AFgk5QAkPPS+OoLuOOozhuaQGC18MxVTnw/06q93gShAJzlwnSCY9YtqA==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/stream": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-12.0.0.tgz", + "integrity": "sha512-9On64rhzuqKdOQyiYLYv2lQOh3TZU/D3+IWCR5gk0alPel2nwpp4YwDEGiUBfrQZEdQ6xww0PWkzqth4wqwX3Q==", "license": "MIT", "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": "^20.19.0 || >=22.12.0" - } - }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@serialport/binding-mock": { - "version": "10.2.2", - "license": "MIT", - "dependencies": { - "@serialport/bindings-interface": "^1.2.1", - "debug": "^4.3.3" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@serialport/bindings-cpp": { - "version": "13.0.0", - "hasInstallScript": true, - "license": "MIT", "dependencies": { "@serialport/bindings-interface": "1.2.2", - "@serialport/parser-readline": "12.0.0", - "debug": "4.4.0", - "node-addon-api": "8.3.0", - "node-gyp-build": "4.8.4" - }, - "engines": { - "node": ">=18.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-delimiter": { - "version": "12.0.0", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-readline": { - "version": "12.0.0", - "license": "MIT", - "dependencies": { - "@serialport/parser-delimiter": "12.0.0" + "debug": "4.3.4" }, "engines": { "node": ">=12.0.0" @@ -2604,11 +2115,14 @@ "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/@serialport/bindings-cpp/node_modules/debug": { - "version": "4.4.0", + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/stream/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "license": "MIT", + "optional": true, "dependencies": { - "ms": "^2.1.3" + "ms": "2.1.2" }, "engines": { "node": ">=6.0" @@ -2619,3130 +2133,1560 @@ } } }, - "node_modules/@serialport/bindings-cpp/node_modules/node-addon-api": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.3.0.tgz", - "integrity": "sha512-8VOpLHFrOQlAH+qA0ZzuGRlALRA6/LVh8QJldbrC4DY0hXoMP0l4Acq8TzFC018HztWiRqyCEj2aTWY2UvnJUg==", - "license": "MIT", - "engines": { - "node": "^18 || ^20 || >= 21" - } - }, - "node_modules/@serialport/bindings-interface": { - "version": "1.2.2", - "license": "MIT", - "engines": { - "node": "^12.22 || ^14.13 || >=16" - } - }, - "node_modules/@serialport/parser-byte-length": { - "version": "13.0.0", - "license": "MIT", - "engines": { - "node": ">=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@serialport/parser-cctalk": { - "version": "13.0.0", - "license": "MIT", - "engines": { - "node": ">=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@serialport/parser-delimiter": { - "version": "13.0.0", - "license": "MIT", - "engines": { - "node": ">=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@serialport/parser-inter-byte-timeout": { - "version": "13.0.0", - "license": "MIT", - "engines": { - "node": ">=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@serialport/parser-packet-length": { - "version": "13.0.0", - "license": "MIT", - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/@serialport/parser-readline": { - "version": "13.0.0", - "license": "MIT", - "dependencies": { - "@serialport/parser-delimiter": "13.0.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@serialport/parser-ready": { - "version": "13.0.0", - "license": "MIT", - "engines": { - "node": ">=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@serialport/parser-regex": { - "version": "13.0.0", - "license": "MIT", - "engines": { - "node": ">=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@serialport/parser-slip-encoder": { - "version": "13.0.0", - "license": "MIT", - "engines": { - "node": ">=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@serialport/parser-spacepacket": { - "version": "13.0.0", - "license": "MIT", - "engines": { - "node": ">=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@serialport/stream": { - "version": "13.0.0", - "license": "MIT", - "dependencies": { - "@serialport/bindings-interface": "1.2.2", - "debug": "4.4.0" - }, - "engines": { - "node": ">=20.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@serialport/stream/node_modules/debug": { - "version": "4.4.0", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@sinclair/typebox": { - "version": "0.34.49", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", - "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", - "license": "MIT" - }, - "node_modules/@sindresorhus/base62": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.2", - "license": "MIT" - }, - "node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "dev": true, - "license": "MIT" - }, - "node_modules/@stoprocent/bluetooth-hci-socket": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/@stoprocent/bluetooth-hci-socket/-/bluetooth-hci-socket-2.2.6.tgz", - "integrity": "sha512-elKIsQe78EnxXzxfA/iuqGufD02d6t3S95eNhFaPvTJs7S3IHq2CdIHwTg3BLwae7Jw14mwK0VhWVtNtzrLyoA==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "linux", - "android", - "freebsd", - "win32", - "darwin" - ], - "dependencies": { - "async": "^3.2.6", - "debug": "^4.3.7", - "node-addon-api": "^8.3.1", - "node-gyp-build": "^4.8.4", - "patch-package": "^8.0.0", - "serialport": "^12.0.0" - }, - "optionalDependencies": { - "usb": "^2.14.0" - } - }, - "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/bindings-cpp": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/@serialport/bindings-cpp/-/bindings-cpp-12.0.1.tgz", - "integrity": "sha512-r2XOwY2dDvbW7dKqSPIk2gzsr6M6Qpe9+/Ngs94fNaNlcTRCV02PfaoDmRgcubpNVVcLATlxSxPTIDw12dbKOg==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@serialport/bindings-interface": "1.2.2", - "@serialport/parser-readline": "11.0.0", - "debug": "4.3.4", - "node-addon-api": "7.0.0", - "node-gyp-build": "4.6.0" - }, - "engines": { - "node": ">=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-delimiter": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-11.0.0.tgz", - "integrity": "sha512-aZLJhlRTjSmEwllLG7S4J8s8ctRAS0cbvCpO87smLvl3e4BgzbVgF6Z6zaJd3Aji2uSiYgfedCdNc4L6W+1E2g==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/bindings-cpp/node_modules/@serialport/parser-readline": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-11.0.0.tgz", - "integrity": "sha512-rRAivhRkT3YO28WjmmG4FQX6L+KMb5/ikhyylRfzWPw0nSXy97+u07peS9CbHqaNvJkMhH1locp2H36aGMOEIA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@serialport/parser-delimiter": "11.0.0" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/bindings-cpp/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/bindings-cpp/node_modules/node-addon-api": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.0.0.tgz", - "integrity": "sha512-vgbBJTS4m5/KkE16t5Ly0WW9hz46swAstv0hYYwMtbG7AznRhNyfLRe8HZAiWIpcHzoO7HxhLuBQj9rJ/Ho0ZA==", - "license": "MIT", - "optional": true - }, - "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/bindings-cpp/node_modules/node-gyp-build": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.6.0.tgz", - "integrity": "sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==", - "license": "MIT", - "optional": true, - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-byte-length": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-byte-length/-/parser-byte-length-12.0.0.tgz", - "integrity": "sha512-0ei0txFAj+s6FTiCJFBJ1T2hpKkX8Md0Pu6dqMrYoirjPskDLJRgZGLqoy3/lnU1bkvHpnJO+9oJ3PB9v8rNlg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-cctalk": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-cctalk/-/parser-cctalk-12.0.0.tgz", - "integrity": "sha512-0PfLzO9t2X5ufKuBO34DQKLXrCCqS9xz2D0pfuaLNeTkyGUBv426zxoMf3rsMRodDOZNbFblu3Ae84MOQXjnZw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-delimiter": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-delimiter/-/parser-delimiter-12.0.0.tgz", - "integrity": "sha512-gu26tVt5lQoybhorLTPsH2j2LnX3AOP2x/34+DUSTNaUTzu2fBXw+isVjQJpUBFWu6aeQRZw5bJol5X9Gxjblw==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-inter-byte-timeout": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-inter-byte-timeout/-/parser-inter-byte-timeout-12.0.0.tgz", - "integrity": "sha512-GnCh8K0NAESfhCuXAt+FfBRz1Cf9CzIgXfp7SdMgXwrtuUnCC/yuRTUFWRvuzhYKoAo1TL0hhUo77SFHUH1T/w==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-packet-length": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-packet-length/-/parser-packet-length-12.0.0.tgz", - "integrity": "sha512-p1hiCRqvGHHLCN/8ZiPUY/G0zrxd7gtZs251n+cfNTn+87rwcdUeu9Dps3Aadx30/sOGGFL6brIRGK4l/t7MuQ==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-readline": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-readline/-/parser-readline-12.0.0.tgz", - "integrity": "sha512-O7cywCWC8PiOMvo/gglEBfAkLjp/SENEML46BXDykfKP5mTPM46XMaX1L0waWU6DXJpBgjaL7+yX6VriVPbN4w==", - "license": "MIT", - "optional": true, - "dependencies": { - "@serialport/parser-delimiter": "12.0.0" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-ready": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-ready/-/parser-ready-12.0.0.tgz", - "integrity": "sha512-ygDwj3O4SDpZlbrRUraoXIoIqb8sM7aMKryGjYTIF0JRnKeB1ys8+wIp0RFMdFbO62YriUDextHB5Um5cKFSWg==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-regex": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-regex/-/parser-regex-12.0.0.tgz", - "integrity": "sha512-dCAVh4P/pZrLcPv9NJ2mvPRBg64L5jXuiRxIlyxxdZGH4WubwXVXY/kBTihQmiAMPxbT3yshSX8f2+feqWsxqA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-slip-encoder": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-slip-encoder/-/parser-slip-encoder-12.0.0.tgz", - "integrity": "sha512-0APxDGR9YvJXTRfY+uRGhzOhTpU5akSH183RUcwzN7QXh8/1jwFsFLCu0grmAUfi+fItCkR+Xr1TcNJLR13VNA==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/parser-spacepacket": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/parser-spacepacket/-/parser-spacepacket-12.0.0.tgz", - "integrity": "sha512-dozONxhPC/78pntuxpz/NOtVps8qIc/UZzdc/LuPvVsqCoJXiRxOg6ZtCP/W58iibJDKPZPAWPGYeZt9DJxI+Q==", - "license": "MIT", - "optional": true, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/stream": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/@serialport/stream/-/stream-12.0.0.tgz", - "integrity": "sha512-9On64rhzuqKdOQyiYLYv2lQOh3TZU/D3+IWCR5gk0alPel2nwpp4YwDEGiUBfrQZEdQ6xww0PWkzqth4wqwX3Q==", - "license": "MIT", - "optional": true, - "dependencies": { - "@serialport/bindings-interface": "1.2.2", - "debug": "4.3.4" - }, - "engines": { - "node": ">=12.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/@serialport/stream/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "license": "MIT", - "optional": true - }, - "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/serialport": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/serialport/-/serialport-12.0.0.tgz", - "integrity": "sha512-AmH3D9hHPFmnF/oq/rvigfiAouAKyK/TjnrkwZRYSFZxNggJxwvbAbfYrLeuvq7ktUdhuHdVdSjj852Z55R+uA==", - "license": "MIT", - "optional": true, - "dependencies": { - "@serialport/binding-mock": "10.2.2", - "@serialport/bindings-cpp": "12.0.1", - "@serialport/parser-byte-length": "12.0.0", - "@serialport/parser-cctalk": "12.0.0", - "@serialport/parser-delimiter": "12.0.0", - "@serialport/parser-inter-byte-timeout": "12.0.0", - "@serialport/parser-packet-length": "12.0.0", - "@serialport/parser-readline": "12.0.0", - "@serialport/parser-ready": "12.0.0", - "@serialport/parser-regex": "12.0.0", - "@serialport/parser-slip-encoder": "12.0.0", - "@serialport/parser-spacepacket": "12.0.0", - "@serialport/stream": "12.0.0", - "debug": "4.3.4" - }, - "engines": { - "node": ">=16.0.0" - }, - "funding": { - "url": "https://opencollective.com/serialport/donate" - } - }, - "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/serialport/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/usb": { - "version": "2.18.0", - "resolved": "https://registry.npmjs.org/usb/-/usb-2.18.0.tgz", - "integrity": "sha512-klAJPUTaSxRvTgrIh7om6UTrgRxdzioD+rJc/MaoiN8+OGdqRzai39tR00asnoCesPNikItWB9zBZoo0pJsWaA==", - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "dependencies": { - "@types/w3c-web-usb": "^1.0.6", - "node-addon-api": "^8.0.0", - "node-gyp-build": "^4.5.0" - }, - "engines": { - "node": ">=12.22.0 <13.0 || >=14.17.0" - } - }, - "node_modules/@stoprocent/noble": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/@stoprocent/noble/-/noble-2.5.5.tgz", - "integrity": "sha512-tHtc8nEvf+J11MedleT0YBhxBrWTqJaoXQJ9H983Gnrus1cHHxs2YtCHMk/3mFuoUAIvW0o1JcH8cHBieqkUYQ==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "debug": "^4.3.7", - "node-addon-api": "^8.1.0", - "node-gyp-build": "^4.8.1", - "patch-package": "^8.0.0" - }, - "engines": { - "node": ">=14" - }, - "optionalDependencies": { - "@stoprocent/bluetooth-hci-socket": "^2.2.6" - }, - "peerDependencies": { - "dbus-next": "^0.10.0" - }, - "peerDependenciesMeta": { - "dbus-next": { - "optional": true - } - } - }, - "node_modules/@stylistic/eslint-plugin": { - "version": "5.10.0", - "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.10.0.tgz", - "integrity": "sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/types": "^8.56.0", - "eslint-visitor-keys": "^4.2.1", - "espree": "^10.4.0", - "estraverse": "^5.3.0", - "picomatch": "^4.0.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "peerDependencies": { - "eslint": "^9.0.0 || ^10.0.0" - } - }, - "node_modules/@stylistic/eslint-plugin/node_modules/eslint-visitor-keys": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", - "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", - "dev": true, - "license": "Apache-2.0", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@stylistic/eslint-plugin/node_modules/espree": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", - "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "acorn": "^8.15.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^4.2.1" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/@stylistic/eslint-plugin/node_modules/picomatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", - "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/@timesplinter/pimple": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@timesplinter/pimple/-/pimple-2.1.1.tgz", - "integrity": "sha512-Sy0nqk5480ZukdvEfx9mCmKMJ+jCnjiXePO7Flf+4RK5W5bs1qyGdExQJZ6tg6SoCKQekRvpaW6GNGl/Zf3WeA==", - "license": "LGPL-3.0-or-later" - }, - "node_modules/@timesplinter/sequential-task-queue": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@timesplinter/sequential-task-queue/-/sequential-task-queue-1.4.0.tgz", - "integrity": "sha512-yXVO3Va7d2UiWyw7ucrsFAbTOtDIrV3DghmSMCg0jRkJNpMW3v65whddK3IXEuaJxpMR2E7UQ7AXLx0410Gdtw==", - "license": "MIT" - }, - "node_modules/@tybys/wasm-util": { - "version": "0.10.3", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", - "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", - "dev": true, - "license": "MIT", - "optional": true, - "dependencies": { - "tslib": "^2.4.0" - } - }, - "node_modules/@types/body-parser": { - "version": "1.19.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "node_modules/@types/chai": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", - "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/deep-eql": "*", - "assertion-error": "^2.0.1" - } - }, - "node_modules/@types/connect": { - "version": "3.4.38", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/cookiejar": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", - "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/cors": { - "version": "2.8.19", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/deep-eql": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", - "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/esrecurse": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", - "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/express": { - "version": "5.0.6", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^5.0.0", - "@types/serve-static": "^2" - } - }, - "node_modules/@types/express-serve-static-core": { - "version": "5.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*", - "@types/send": "*" - } - }, - "node_modules/@types/http-errors": { - "version": "2.0.5", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/json-schema": { - "version": "7.0.15", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/methods": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", - "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/node": { - "version": "24.13.2", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.2.tgz", - "integrity": "sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==", - "license": "MIT", - "dependencies": { - "undici-types": "~7.18.0" - } - }, - "node_modules/@types/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/range-parser": { - "version": "1.2.7", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/send": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@types/serve-static": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/http-errors": "*", - "@types/node": "*" - } - }, - "node_modules/@types/superagent": { - "version": "8.1.10", - "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.10.tgz", - "integrity": "sha512-nbt4IWXABhW0jGmmpRzCFNlbmwCTzZ2gTUsNIr+X+ItdqPms+PAJZbWsNzpS2USqXjcoNLQcO6nXo60zcPQiIg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/cookiejar": "^2.1.5", - "@types/methods": "^1.1.4", - "@types/node": "*", - "form-data": "^4.0.0" - } - }, - "node_modules/@types/supertest": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-7.2.0.tgz", - "integrity": "sha512-uh2Lv57xvggst6lCqNdFAmDSvoMG7M/HDtX4iUCquxQ5EGPtaPM5PL5Hmi7LCvOG8db7YaCPNJEeoI8s/WzIQw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/methods": "^1.1.4", - "@types/superagent": "^8.1.0" - } - }, - "node_modules/@types/type-detect": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@types/type-detect/-/type-detect-4.0.3.tgz", - "integrity": "sha512-+VBjBovVTOL4Z5Q10GzTFeKseEeJbBAQ2avjT1Fx+sko/RuhQBdZJB+Swvip1eDF6qIm7iAKfTcG4QSxWhfBaw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@types/w3c-web-usb": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.14.tgz", - "integrity": "sha512-Qu3Nn6JFuF4+sHKYl+IcX9vYiI40ogleXzFFSxoE1W94rG98o/kXs8uJ0QSfFzuwBCZWlGfUGpPkgwuuX4PchA==", - "license": "MIT" - }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, - "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.62.0.tgz", - "integrity": "sha512-o+mpz7EYiMzXoySXiKmzlabIvTVqUuK5yLrAedRPRDA0IpPFMUV1IXt6OqljIxX/kumN6EjUYp41Hqelh6p/Dw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.62.0", - "@typescript-eslint/type-utils": "8.62.0", - "@typescript-eslint/utils": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0", - "ignore": "^7.0.5", - "natural-compare": "^1.4.0", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "@typescript-eslint/parser": "^8.62.0", - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { - "version": "7.0.5", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@typescript-eslint/parser": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.62.0.tgz", - "integrity": "sha512-dzHeT2gySzZtLDsuqxU9AkYgIsQoHAHtRBpOqM+Ofzx1Bwrd2RcCjQJ+6iQbsHOIR6NS33bF2W1k3blN1zLDrA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/scope-manager": "8.62.0", - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/typescript-estree": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/project-service": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.62.0.tgz", - "integrity": "sha512-wexnCqiTg7BOGtbLDftYpRWlmLq4xfoMd7BKFR6Y75sZS3QmRKLdN3yWLhmIYgqMmP/OXWpj3H8odkb5nGURCQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.62.0", - "@typescript-eslint/types": "^8.62.0", - "debug": "^4.4.3" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/scope-manager": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.62.0.tgz", - "integrity": "sha512-1lX38kNxXIRb8mEc3lbq5mdHq1Pf2+U0nFU65KfT18mtPxxl0fvjuEE92mHuXPuCtElJhOrddOpyMlM3Z0umEA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.62.0.tgz", - "integrity": "sha512-y2GAdB6ykaXUvuspbYnizQc4oDDz0Tz/Yc7iWrXf9mx8vm/L/0vLHCe0tS2boG96Zy+DivnVDQ9ZUEWoHqqx1g==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/type-utils": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.62.0.tgz", - "integrity": "sha512-+g5O3j0w2ldzC86Pv6fvbO/xhAonbJFIdf/MKQ1d30gndlsVzUOE83ldfSE15Qrl9fhFjK6AovHs5Wpp6vx86w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/typescript-estree": "8.62.0", - "@typescript-eslint/utils": "8.62.0", - "debug": "^4.4.3", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/types": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.62.0.tgz", - "integrity": "sha512-KvAclkktORPvM54TgLgA4z9HIV1M8zOgw9ZVNXl9f/8dLYfXYX1wkMXP7qmabpijQRV5bHJLOmoyGQbLMaUYeg==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.62.0.tgz", - "integrity": "sha512-+hVbNxtW64pIcZWDPGbyaKF7vp2IBTVY5ma1blwwksrjdsbdqqEKvJWMGbBofei4F6Dovx1M0RJgoFeNu2279A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/project-service": "8.62.0", - "@typescript-eslint/tsconfig-utils": "8.62.0", - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/visitor-keys": "8.62.0", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/@typescript-eslint/utils": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.62.0.tgz", - "integrity": "sha512-82r66fi9zYwZ+mTq3vKgwjbZ1PVk/DJzrXFLpG6RnBbdvH8TEGVHIs9H4d2drhkOzf0syZuD/OZvvlu6GDbP4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.62.0", - "@typescript-eslint/types": "8.62.0", - "@typescript-eslint/typescript-estree": "8.62.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" - } - }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.62.0.tgz", - "integrity": "sha512-CY3uyFSRbcQv3nnSv8S0+lDftMVz6P963PoRlxrV7ew/Md564g9ut60PYzdLM5qW4jFn93GBF+Soi90ISAN+GQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "8.62.0", - "eslint-visitor-keys": "^5.0.0" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - } - }, - "node_modules/@vitest/coverage-v8": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.9.tgz", - "integrity": "sha512-G9/lgqibheLVBDRuya45EbsEXTYcWoSG+TLg7i2axuzx0Eq62eXn+aWXyaVdV5vKvFSWd6ywcX8hA7la9Pvu8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@bcoe/v8-coverage": "^1.0.2", - "@vitest/utils": "4.1.9", - "ast-v8-to-istanbul": "^1.0.0", - "istanbul-lib-coverage": "^3.2.2", - "istanbul-lib-report": "^3.0.1", - "istanbul-reports": "^3.2.0", - "magicast": "^0.5.2", - "obug": "^2.1.1", - "std-env": "^4.0.0-rc.1", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "@vitest/browser": "4.1.9", - "vitest": "4.1.9" - }, - "peerDependenciesMeta": { - "@vitest/browser": { - "optional": true - } - } - }, - "node_modules/@vitest/expect": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.9.tgz", - "integrity": "sha512-vl/rYsUKcBr3SnQn166+XR5ZQcgMx3DQhFWdfli/cWpLnLUmbxZvyrJZotLFUryib+LtArYMSTJ5RbQ57ZqrlA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@standard-schema/spec": "^1.1.0", - "@types/chai": "^5.2.2", - "@vitest/spy": "4.1.9", - "@vitest/utils": "4.1.9", - "chai": "^6.2.2", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/mocker": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.9.tgz", - "integrity": "sha512-EVkXzBjrPGM+cK8/ANWgBrkUCfJfb38/EfTSO8h7pWvKkyPkpWxvR7BkD2MyItMF62C97zAEoqdpUixwR/e+Rw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/spy": "4.1.9", - "estree-walker": "^3.0.3", - "magic-string": "^0.30.21" - }, - "funding": { - "url": "https://opencollective.com/vitest" - }, - "peerDependencies": { - "msw": "^2.4.9", - "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "msw": { - "optional": true - }, - "vite": { - "optional": true - } - } - }, - "node_modules/@vitest/pretty-format": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.9.tgz", - "integrity": "sha512-s0iufns3iIFitdgm+YR7g1whCAaGtXz459VS9/PqyKDEEFgYIhsHOQmXgIgDuYCt7DeQmiZT0Qe2OA2p4ZPu5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/runner": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.9.tgz", - "integrity": "sha512-KXLMDtc7oe70+3mJfGrPUWPesswH+3sTxAMAMl8DG7I8IUQT4XW718dY5ID3vPUcmlu27CcKfY4P3h3I29SLJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/utils": "4.1.9", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/snapshot": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.9.tgz", - "integrity": "sha512-Jc7RKGNBo8Z28WYIm0Niej4xdSPByRf6mU58VpHQkd6Zh05rlnA+twjbK5HyeIGHxrzsc3mJgS43uM0CZKzaIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.9", - "@vitest/utils": "4.1.9", - "magic-string": "^0.30.21", - "pathe": "^2.0.3" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/spy": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.9.tgz", - "integrity": "sha512-fHpsS6mIi+PiEW+vcRVOMkX1oSaPKne3VOclSFICPcGOmfKgXPU5iAah+wcNcj2xPrCCmfq99IDGf+EojhhvhA==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@vitest/utils": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.9.tgz", - "integrity": "sha512-A51o8ymO5PpqlWNnBP9ZHPXDIpuMtTLlGSjN7la4US+LJzoUMyhwjA5QXlm39JexgwHKW4Xjs8Z2d3dLCXOeuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vitest/pretty-format": "4.1.9", - "convert-source-map": "^2.0.0", - "tinyrainbow": "^3.1.0" - }, - "funding": { - "url": "https://opencollective.com/vitest" - } - }, - "node_modules/@yarnpkg/lockfile": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", - "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", - "license": "BSD-2-Clause" - }, - "node_modules/accepts": { - "version": "2.0.0", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "8.17.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", - "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", - "dev": true, - "license": "MIT", - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "dev": true, - "license": "MIT", - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-formats": { - "version": "3.0.1", - "license": "MIT", - "dependencies": { - "ajv": "^8.0.0" - }, - "peerDependencies": { - "ajv": "^8.0.0" - }, - "peerDependenciesMeta": { - "ajv": { - "optional": true - } - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/any-promise": { - "version": "1.3.0", - "license": "MIT" - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/are-docs-informative": { - "version": "0.0.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - } - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "is-array-buffer": "^3.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.reduce": { - "version": "1.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-array-method-boxes-properly": "^1.0.0", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "is-string": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.5", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/asap": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", - "dev": true, - "license": "MIT" - }, - "node_modules/assertion-error": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", - "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/ast-v8-to-istanbul": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.4.tgz", - "integrity": "sha512-0bC0/4bTSrnwdhU3IsZDwEdojvuPrSg59OYZfKsLRtJZ0u8VBx9DebfqqG8bRdCC0I7vjgxmPi41P0lpkhJHtA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "^0.3.31", - "estree-walker": "^3.0.3", - "js-tokens": "^10.0.0" - } - }, - "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { - "version": "10.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", - "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/async": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", - "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "license": "MIT", "optional": true }, - "node_modules/async-function": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true, - "license": "MIT" - }, - "node_modules/atomic-sleep": { - "version": "1.0.0", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "dev": true, - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.17", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.17.tgz", - "integrity": "sha512-aTyf30K/rqAsNwN76zYrdtx8obu0E4KoUME29B1xj+B3WxgvWkp943vYQ+z8Mv3lw9xHXMHpvSPOBxzAkIa94w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-define-polyfill-provider": "^0.6.8", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.14.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.14.2.tgz", - "integrity": "sha512-coWpDLJ410R781Npmn/SIBZEsAetR4xVi0SxLMXPaMO4lSf1MwnkGYMtkFxew0Dn8B3/CpbpYxN0JCgg8mn67g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.8", - "core-js-compat": "^3.48.0" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.8", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.8.tgz", - "integrity": "sha512-M762rNHfSF1EV3SLtnCJXFoQbbIIz0OyRwnCmV0KPC7qosSfCO0QLTSuJX3ayAebubhE6oYBAYPrBA5ljowaZg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.8" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/base64id": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": "^4.5.0 || >= 5.9" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.40", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.40.tgz", - "integrity": "sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "baseline-browser-mapping": "dist/cli.cjs" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/serialport": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/serialport/-/serialport-12.0.0.tgz", + "integrity": "sha512-AmH3D9hHPFmnF/oq/rvigfiAouAKyK/TjnrkwZRYSFZxNggJxwvbAbfYrLeuvq7ktUdhuHdVdSjj852Z55R+uA==", "license": "MIT", + "optional": true, "dependencies": { - "bytes": "^3.1.2", - "content-type": "^2.0.0", - "debug": "^4.4.3", - "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", - "on-finished": "^2.4.1", - "qs": "^6.15.2", - "raw-body": "^3.0.2", - "type-is": "^2.1.0" - }, - "engines": { - "node": ">=18" + "@serialport/binding-mock": "10.2.2", + "@serialport/bindings-cpp": "12.0.1", + "@serialport/parser-byte-length": "12.0.0", + "@serialport/parser-cctalk": "12.0.0", + "@serialport/parser-delimiter": "12.0.0", + "@serialport/parser-inter-byte-timeout": "12.0.0", + "@serialport/parser-packet-length": "12.0.0", + "@serialport/parser-readline": "12.0.0", + "@serialport/parser-ready": "12.0.0", + "@serialport/parser-regex": "12.0.0", + "@serialport/parser-slip-encoder": "12.0.0", + "@serialport/parser-spacepacket": "12.0.0", + "@serialport/stream": "12.0.0", + "debug": "4.3.4" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/body-parser/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", "engines": { - "node": ">=18" + "node": ">=16.0.0" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://opencollective.com/serialport/donate" } }, - "node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", - "dev": true, + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/serialport/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "license": "MIT", + "optional": true, "dependencies": { - "balanced-match": "^4.0.2" + "ms": "2.1.2" }, "engines": { - "node": "18 || 20 || >=22" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "node_modules/@stoprocent/bluetooth-hci-socket/node_modules/usb": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/usb/-/usb-2.18.0.tgz", + "integrity": "sha512-klAJPUTaSxRvTgrIh7om6UTrgRxdzioD+rJc/MaoiN8+OGdqRzai39tR00asnoCesPNikItWB9zBZoo0pJsWaA==", + "hasInstallScript": true, "license": "MIT", + "optional": true, "dependencies": { - "fill-range": "^7.1.1" + "@types/w3c-web-usb": "^1.0.6", + "node-addon-api": "^8.0.0", + "node-gyp-build": "^4.5.0" }, "engines": { - "node": ">=8" + "node": ">=12.22.0 <13.0 || >=14.17.0" } }, - "node_modules/browserslist": { - "version": "4.28.4", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.4.tgz", - "integrity": "sha512-MTc8i/x9jBQd1iMw2CFGS+rwMa07eYjLR0CCTLDACl9xhxy+nIs3KeML/biicXtk9JrZ6dnnTatmc7ErPXIxqw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], + "node_modules/@stoprocent/noble": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@stoprocent/noble/-/noble-2.7.1.tgz", + "integrity": "sha512-tOGceVpRd7XhcEe65wqgKONPu7wZ7DOXF94TEqM7Rqg82e9U7v+CurK85L7CEq3YKK21OAJWMXSSu19lCxaCUA==", + "hasInstallScript": true, "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.38", - "caniuse-lite": "^1.0.30001799", - "electron-to-chromium": "^1.5.376", - "node-releases": "^2.0.48", - "update-browserslist-db": "^1.2.3" - }, - "bin": { - "browserslist": "cli.js" + "debug": "^4.3.7", + "node-addon-api": "^8.1.0", + "node-gyp-build": "^4.8.1", + "patch-package": "^8.0.0" }, "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + "node": ">=14" + }, + "optionalDependencies": { + "@stoprocent/bluetooth-hci-socket": "^2.3.0" + }, + "peerDependencies": { + "dbus-next": "^0.10.0" + }, + "peerDependenciesMeta": { + "dbus-next": { + "optional": true + } } }, - "node_modules/buffer-alloc": { - "version": "1.2.0", + "node_modules/@stylistic/eslint-plugin": { + "version": "5.10.0", + "resolved": "https://registry.npmjs.org/@stylistic/eslint-plugin/-/eslint-plugin-5.10.0.tgz", + "integrity": "sha512-nPK52ZHvot8Ju/0A4ucSX1dcPV2/1clx0kLcH5wDmrE4naKso7TUC/voUyU1O9OTKTrR6MYip6LP0ogEMQ9jPQ==", + "dev": true, "license": "MIT", "dependencies": { - "buffer-alloc-unsafe": "^1.1.0", - "buffer-fill": "^1.0.0" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/types": "^8.56.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "estraverse": "^5.3.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "peerDependencies": { + "eslint": "^9.0.0 || ^10.0.0" } }, - "node_modules/buffer-alloc-unsafe": { - "version": "1.1.0", - "license": "MIT" + "node_modules/@timesplinter/pimple": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@timesplinter/pimple/-/pimple-2.1.1.tgz", + "integrity": "sha512-Sy0nqk5480ZukdvEfx9mCmKMJ+jCnjiXePO7Flf+4RK5W5bs1qyGdExQJZ6tg6SoCKQekRvpaW6GNGl/Zf3WeA==", + "license": "LGPL-3.0-or-later" }, - "node_modules/buffer-fill": { - "version": "1.0.0", + "node_modules/@timesplinter/sequential-task-queue": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@timesplinter/sequential-task-queue/-/sequential-task-queue-1.4.0.tgz", + "integrity": "sha512-yXVO3Va7d2UiWyw7ucrsFAbTOtDIrV3DghmSMCg0jRkJNpMW3v65whddK3IXEuaJxpMR2E7UQ7AXLx0410Gdtw==", "license": "MIT" }, - "node_modules/buffer-from": { - "version": "1.1.2", + "node_modules/@tybys/wasm-util": { + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "dev": true, - "license": "MIT" - }, - "node_modules/buttplug": { - "version": "3.2.2", - "license": "BSD-3-Clause", - "dependencies": { - "class-transformer": "^0.5.1", - "eventemitter3": "^5.0.1", - "reflect-metadata": "^0.2.1", - "ws": "^8.16.0" - } - }, - "node_modules/buttplug/node_modules/reflect-metadata": { - "version": "0.2.2", - "license": "Apache-2.0" - }, - "node_modules/bytes": { - "version": "3.1.2", "license": "MIT", - "engines": { - "node": ">= 0.8" + "optional": true, + "dependencies": { + "tslib": "^2.4.0" } }, - "node_modules/call-bind": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", - "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", + "node_modules/@types/body-parser": { + "version": "1.19.6", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.6.tgz", + "integrity": "sha512-HLFeCYgz89uk22N5Qg3dvGvsv46B8GLvKKo1zKG4NybA8U2DiEO3w9lqGg29t/tfLRJpJ6iQxnVw4OnB7MoM9g==", + "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "get-intrinsic": "^1.3.0", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@types/connect": "*", + "@types/node": "*" } }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" } }, - "node_modules/call-bound": { - "version": "1.0.4", + "node_modules/@types/connect": { + "version": "3.4.38", + "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", + "integrity": "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==", + "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@types/node": "*" } }, - "node_modules/caniuse-lite": { - "version": "1.0.30001799", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001799.tgz", - "integrity": "sha512-hG1bReV+OUU+MOqK4t/ZWI0tZOyz3rqS9XuhOUz1cIcbwBKjOyJEJuw9ER5JuNyqxNk8u/JUVbGibBOL1yrjFw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "CC-BY-4.0" - }, - "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "node_modules/@types/cookiejar": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@types/cookiejar/-/cookiejar-2.1.5.tgz", + "integrity": "sha512-he+DHOWReW0nghN24E1WUqM0efK4kI9oTqDm6XmK8ZPe2djZ90BSNdGnIyCLzCPw7/pogPlGbzI2wHGGmi4O/Q==", "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } + "license": "MIT" }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", "license": "MIT", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "@types/node": "*" } }, - "node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "license": "MIT", - "dependencies": { - "readdirp": "^5.0.0" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "license": "MIT", - "engines": { - "node": ">=8" - } + "node_modules/@types/esrecurse": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@types/esrecurse/-/esrecurse-4.3.1.tgz", + "integrity": "sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==", + "dev": true, + "license": "MIT" }, - "node_modules/class-transformer": { - "version": "0.5.1", + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, "license": "MIT" }, - "node_modules/clone-deep": { - "version": "4.0.1", + "node_modules/@types/express": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/@types/express/-/express-5.0.6.tgz", + "integrity": "sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==", "dev": true, "license": "MIT", "dependencies": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - }, - "engines": { - "node": ">=6" + "@types/body-parser": "*", + "@types/express-serve-static-core": "^5.0.0", + "@types/serve-static": "^2" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@types/express-serve-static-core": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-5.1.3.tgz", + "integrity": "sha512-dPfW8NFiOF4wOHc7+N/QSxlY9cfSsenewGbAz8C8U/MULPd/YZ27LvJUIlzaXie7e6Ove9YunJGgC9tbHD2cKw==", + "dev": true, "license": "MIT", "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/@types/http-errors": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", + "integrity": "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==", + "dev": true, "license": "MIT" }, - "node_modules/colorette": { - "version": "2.0.20", + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", "dev": true, "license": "MIT" }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/@types/methods": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@types/methods/-/methods-1.1.4.tgz", + "integrity": "sha512-ymXWVrDiCxTBE3+RIrrP533E70eA+9qu7zdWoHuOmGujkYtzf4HQF96b8nwHLqhuf4ykX61IGRIB38CC6/sImQ==", "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.13.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.13.3.tgz", + "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", "license": "MIT", "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" + "undici-types": "~7.18.0" } }, - "node_modules/commander": { - "version": "6.2.1", + "node_modules/@types/qs": { + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-GZHUBZR9hckSUhrxmp1nG6NwdpM9fCunJwyThLW1X3AyHgd9IlHb6VANpQQqDr2o/qQp6McZ3y/IA2rVzKzSbw==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 6" - } + "license": "MIT" }, - "node_modules/comment-parser": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.6.tgz", - "integrity": "sha512-ObxuY6vnbWTN6Od72xfwN9DbzC7Y2vv8u1Soi9ahRKL37gb6y1qk6/dgjs+3JWuXJHWvsg3BXIwzd/rkmAwavg==", + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 12.0.0" - } + "license": "MIT" }, - "node_modules/commondir": { - "version": "1.0.1", + "node_modules/@types/send": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@types/send/-/send-1.2.1.tgz", + "integrity": "sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "@types/node": "*" + } }, - "node_modules/component-emitter": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", - "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", + "node_modules/@types/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==", "dev": true, "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*" } }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "node_modules/@types/superagent": { + "version": "8.1.11", + "resolved": "https://registry.npmjs.org/@types/superagent/-/superagent-8.1.11.tgz", + "integrity": "sha512-KA7srSW/HENDtOw9DOqaFLgWuMqN9WgjEw62lh9dpvRaZDkhdOkazASd7X7i2eMUYLHa1U37ZttnePsH5zTDHw==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "dependencies": { + "@types/cookiejar": "^2.1.5", + "@types/methods": "^1.1.4", + "@types/node": "*", + "form-data": "^4.0.0" } }, - "node_modules/content-type": { - "version": "1.0.5", + "node_modules/@types/supertest": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-7.2.1.tgz", + "integrity": "sha512-4CbBvoYVLHL7+yhbYrZET0vsvuyXTC05aRe7dNQkwMzm56auceoy6Yu3K50uZmwfHna1os3CMSgM/3QVkUtPTw==", + "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.6" + "dependencies": { + "@types/methods": "^1.1.4", + "@types/superagent": "^8.1.0" } }, - "node_modules/convert-source-map": { - "version": "2.0.0", + "node_modules/@types/type-detect": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/type-detect/-/type-detect-4.0.3.tgz", + "integrity": "sha512-+VBjBovVTOL4Z5Q10GzTFeKseEeJbBAQ2avjT1Fx+sko/RuhQBdZJB+Swvip1eDF6qIm7iAKfTcG4QSxWhfBaw==", "dev": true, "license": "MIT" }, - "node_modules/cookie": { - "version": "0.7.2", + "node_modules/@types/w3c-web-usb": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.14.tgz", + "integrity": "sha512-Qu3Nn6JFuF4+sHKYl+IcX9vYiI40ogleXzFFSxoE1W94rG98o/kXs8uJ0QSfFzuwBCZWlGfUGpPkgwuuX4PchA==", + "license": "MIT" + }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", "license": "MIT", - "engines": { - "node": ">= 0.6" + "dependencies": { + "@types/node": "*" } }, - "node_modules/cookie-signature": { - "version": "1.2.2", + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.66.0.tgz", + "integrity": "sha512-p088eaGrzYz1s+7cov0aMOCkNGTJlVxF4jgubf28c8L0Cv9Rloj8YBHnv4hXLq6IIEE1AsjNWavO+k+8kP2Y0A==", + "dev": true, "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.66.0", + "@typescript-eslint/type-utils": "8.66.0", + "@typescript-eslint/utils": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.5.0" + }, "engines": { - "node": ">=6.6.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.66.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/cookiejar": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", - "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz", + "integrity": "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">= 4" + } }, - "node_modules/core-js": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.49.0.tgz", - "integrity": "sha512-es1U2+YTtzpwkxVLwAFdSpaIMyQaq0PBgm3YD1W3Qpsn1NAmO3KSgZfu+oGSWVu6NvLHoHCV/aYcsE5wiB7ALg==", + "node_modules/@typescript-eslint/parser": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.66.0.tgz", + "integrity": "sha512-X6ypGChaWYk6PBtUg2BwuTZEFFcHJAtGTVJ9/lCTOufhZ4i9fNolQNnktq+kkMCwMj7V8Svsq7+TxSDslmhE0g==", "dev": true, - "hasInstallScript": true, "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/core-js" + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/core-js-compat": { - "version": "3.49.0", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.49.0.tgz", - "integrity": "sha512-VQXt1jr9cBz03b331DFDCCP90b3fanciLkgiOoy8SBHy06gNf+vQ1A3WFLqG7I8TipYIKeYK9wxd0tUrvHcOZA==", + "node_modules/@typescript-eslint/project-service": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.66.0.tgz", + "integrity": "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==", "dev": true, "license": "MIT", "dependencies": { - "browserslist": "^4.28.1" + "@typescript-eslint/tsconfig-utils": "^8.66.0", + "@typescript-eslint/types": "^8.66.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/core-js" + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/cors": { - "version": "2.8.6", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", - "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.66.0.tgz", + "integrity": "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==", + "dev": true, "license": "MIT", "dependencies": { - "object-assign": "^4", - "vary": "^1" + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0" }, "engines": { - "node": ">= 0.10" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/cross-spawn": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", - "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.66.0.tgz", + "integrity": "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==", + "dev": true, "license": "MIT", - "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, "engines": { - "node": ">= 8" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/data-view-buffer": { - "version": "1.0.2", + "node_modules/@typescript-eslint/type-utils": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.66.0.tgz", + "integrity": "sha512-LG2dWfjZQQp0ADtAu/EWJVayefGL2UEZ3CDeI44D9v3rXB/WYUqE/jpO28KrEKul5AySrmI+Zh1v6v+xW2U9+g==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0", + "@typescript-eslint/utils": "8.66.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/data-view-byte-length": { - "version": "1.0.2", + "node_modules/@typescript-eslint/types": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.66.0.tgz", + "integrity": "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==", "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.2" - }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/inspect-js" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/data-view-byte-offset": { - "version": "1.0.1", + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.66.0.tgz", + "integrity": "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "@typescript-eslint/project-service": "8.66.0", + "@typescript-eslint/tsconfig-utils": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/dateformat": { - "version": "4.6.3", + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, - "license": "MIT", - "engines": { - "node": "*" - } - }, - "node_modules/debug": { - "version": "4.4.3", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" + "license": "ISC", + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=10" } }, - "node_modules/deep-is": { - "version": "0.1.4", + "node_modules/@typescript-eslint/utils": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.66.0.tgz", + "integrity": "sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA==", "dev": true, - "license": "MIT" - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/define-properties": { - "version": "1.2.1", + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.66.0.tgz", + "integrity": "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==", "dev": true, "license": "MIT", "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" + "@typescript-eslint/types": "8.66.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/depd": { - "version": "2.0.0", - "license": "MIT", - "engines": { - "node": ">= 0.8" + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, "license": "Apache-2.0", "engines": { - "node": ">=8" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/dezalgo": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", - "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "node_modules/@vitest/coverage-v8": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.1.10.tgz", + "integrity": "sha512-IM49HmthevbgAO4anp1hwtoT9wYe59w0LR00gr+eagHE+ZJ5lK4sLPeO0ubgoJcwLk6dehU3R24N+FbEEKDc8g==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "asap": "^2.0.0", - "wrappy": "1" - } - }, - "node_modules/dotenv": { - "version": "17.4.2", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", - "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" + "@bcoe/v8-coverage": "^1.0.2", + "@vitest/utils": "4.1.10", + "ast-v8-to-istanbul": "^1.0.0", + "istanbul-lib-coverage": "^3.2.2", + "istanbul-lib-report": "^3.0.1", + "istanbul-reports": "^3.2.0", + "magicast": "^0.5.2", + "obug": "^2.1.1", + "std-env": "^4.0.0-rc.1", + "tinyrainbow": "^3.1.0" }, "funding": { - "url": "https://dotenvx.com" + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@vitest/browser": "4.1.10", + "vitest": "4.1.10" + }, + "peerDependenciesMeta": { + "@vitest/browser": { + "optional": true + } } }, - "node_modules/dunder-proto": { - "version": "1.0.1", + "node_modules/@vitest/expect": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.10.tgz", + "integrity": "sha512-YsCn+qAk1GWjQOWFEsEcL2gNQ0zmVmQu3T03qP6UyjhtmdtwtbuI+DASn/7iQB3HGTXkdBwGddzxPlmiql5vlA==", + "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.1", - "es-errors": "^1.3.0", - "gopd": "^1.2.0" + "@standard-schema/spec": "^1.1.0", + "@types/chai": "^5.2.2", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", + "chai": "^6.2.2", + "tinyrainbow": "^3.1.0" }, - "engines": { - "node": ">= 0.4" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "license": "MIT" - }, - "node_modules/electron-to-chromium": { - "version": "1.5.380", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.380.tgz", - "integrity": "sha512-W6d5AbuEoRayO447cqrg6lKJIlscgRnnxOZl/08kfV71BQDoEBC7Wwis68z87LjyK6f4kWyTaubuDbhHKrZkbA==", + "node_modules/@vitest/mocker": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-4.1.10.tgz", + "integrity": "sha512-v0xaezt+DKEmKfaxg133ldzADrwLGd7Ze1MfQQTYfvs8OqZIwbxyxaYURivwV7sWy5fqn3rH5uOrSp07bp44Ow==", "dev": true, - "license": "ISC" - }, - "node_modules/encodeurl": { - "version": "2.0.0", "license": "MIT", - "engines": { - "node": ">= 0.8" + "dependencies": { + "@vitest/spy": "4.1.10", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.21" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } } }, - "node_modules/end-of-stream": { - "version": "1.4.5", + "node_modules/@vitest/pretty-format": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-4.1.10.tgz", + "integrity": "sha512-W1HsjSH4MXQ9YfmmhLAoIYf1HRfekQCGngeIgcei6MP5QQGWUe0gkopdZQaVCFO+JDJMrAJGwa5pRpNpvy4P8Q==", "dev": true, "license": "MIT", "dependencies": { - "once": "^1.4.0" + "tinyrainbow": "^3.1.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/engine.io": { - "version": "6.6.9", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.9.tgz", - "integrity": "sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg==", + "node_modules/@vitest/runner": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-4.1.10.tgz", + "integrity": "sha512-IKI6kpIH+LmpROplyLwBBaCfMgOZOMsygVa6BARD6ahA04VRuJSa6OaVG7kRvSEMD870Vd91rSSw0eegtWyLGg==", + "dev": true, "license": "MIT", "dependencies": { - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "@types/ws": "^8.5.12", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.7.2", - "cors": "~2.8.5", - "debug": "~4.4.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.21.0" + "@vitest/utils": "4.1.10", + "pathe": "^2.0.3" }, - "engines": { - "node": ">=10.2.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/engine.io-client": { - "version": "6.6.6", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.6.tgz", - "integrity": "sha512-iY6QdftLQ9pyiPoX082bpf/u1UewnOaJrtJIF9T0++QB34lZrj0uP+Q/bj8AlUsAxqhnkTV2BS8SBZSxOmoV5Q==", + "node_modules/@vitest/snapshot": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-4.1.10.tgz", + "integrity": "sha512-xRkfOT1qpTAi/Ti4Y1LtfRc3kEuqxGw59eN2jN9pRWMtS/XDevekhcFSqvQqjUNGksfjMJu3Y+oJ+4Ypn2OaJw==", "dev": true, "license": "MIT", "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.4.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.21.0", - "xmlhttprequest-ssl": "~2.1.1" + "@vitest/pretty-format": "4.1.10", + "@vitest/utils": "4.1.10", + "magic-string": "^0.30.21", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/engine.io-parser": { - "version": "5.2.3", + "node_modules/@vitest/spy": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-4.1.10.tgz", + "integrity": "sha512-PLf/Ugvoq5wO/b4rwYCR1h2PSIdXz7wnkQFMiUpLdtM7l6pqVFcQIBEHyT1+l+cj7mNwAfZHzqXqDyjvOuwbDw==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=10.0.0" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/engine.io/node_modules/accepts": { - "version": "1.3.8", + "node_modules/@vitest/utils": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-4.1.10.tgz", + "integrity": "sha512-fy9am/HWxbaGt/Sawrp90vt6Y6jQwf1RX77cz3uwoJwJVMli/e1IEwRPnMNJ7vKfPTwo0diXifkpPvwH9v7nGA==", + "dev": true, "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "@vitest/pretty-format": "4.1.10", + "convert-source-map": "^2.0.0", + "tinyrainbow": "^3.1.0" }, - "engines": { - "node": ">= 0.6" + "funding": { + "url": "https://opencollective.com/vitest" } }, - "node_modules/engine.io/node_modules/mime-db": { - "version": "1.52.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } + "node_modules/@yarnpkg/lockfile": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz", + "integrity": "sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ==", + "license": "BSD-2-Clause" }, - "node_modules/engine.io/node_modules/mime-types": { - "version": "2.1.35", + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { "node": ">= 0.6" } }, - "node_modules/engine.io/node_modules/negotiator": { - "version": "0.6.3", + "node_modules/acorn": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.18.0.tgz", + "integrity": "sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==", + "dev": true, "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, "engines": { - "node": ">= 0.6" + "node": ">=0.4.0" } }, - "node_modules/error-class-utils": { - "version": "4.0.1", + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=18.18.0" + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/error-custom-class": { - "version": "10.0.1", + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", "dependencies": { - "error-class-utils": "^4.0.1" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, - "engines": { - "node": ">=18.18.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/es-abstract": { - "version": "1.24.2", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.2.tgz", - "integrity": "sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==", - "dev": true, + "node_modules/ajv-formats": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "license": "MIT", "dependencies": { - "array-buffer-byte-length": "^1.0.2", - "arraybuffer.prototype.slice": "^1.0.4", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "data-view-buffer": "^1.0.2", - "data-view-byte-length": "^1.0.2", - "data-view-byte-offset": "^1.0.1", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "es-set-tostringtag": "^2.1.0", - "es-to-primitive": "^1.3.0", - "function.prototype.name": "^1.1.8", - "get-intrinsic": "^1.3.0", - "get-proto": "^1.0.1", - "get-symbol-description": "^1.1.0", - "globalthis": "^1.0.4", - "gopd": "^1.2.0", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "internal-slot": "^1.1.0", - "is-array-buffer": "^3.0.5", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.2", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.2.1", - "is-set": "^2.0.3", - "is-shared-array-buffer": "^1.0.4", - "is-string": "^1.1.1", - "is-typed-array": "^1.1.15", - "is-weakref": "^1.1.1", - "math-intrinsics": "^1.1.0", - "object-inspect": "^1.13.4", - "object-keys": "^1.1.1", - "object.assign": "^4.1.7", - "own-keys": "^1.0.1", - "regexp.prototype.flags": "^1.5.4", - "safe-array-concat": "^1.1.3", - "safe-push-apply": "^1.0.0", - "safe-regex-test": "^1.1.0", - "set-proto": "^1.0.0", - "stop-iteration-iterator": "^1.1.0", - "string.prototype.trim": "^1.2.10", - "string.prototype.trimend": "^1.0.9", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.3", - "typed-array-byte-length": "^1.0.3", - "typed-array-byte-offset": "^1.0.4", - "typed-array-length": "^1.0.7", - "unbox-primitive": "^1.1.0", - "which-typed-array": "^1.1.19" + "ajv": "^8.0.0" }, - "engines": { - "node": ">= 0.4" + "peerDependencies": { + "ajv": "^8.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependenciesMeta": { + "ajv": { + "optional": true + } } }, - "node_modules/es-abstract-get": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-abstract-get/-/es-abstract-get-1.0.0.tgz", - "integrity": "sha512-6PMWXpdhshVvFp+FoWYs1EvG1Nj0tvk0dZM+XcK0xMEM1czRVcP6ohqPWHy6qPagSpC8j4+p89WXlT+xXJs/fg==", - "dev": true, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.2", - "is-callable": "^1.2.7", - "object-inspect": "^1.13.4" + "color-convert": "^2.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/es-array-method-boxes-properly": { - "version": "1.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/es-define-property": { - "version": "1.0.1", - "license": "MIT", - "engines": { - "node": ">= 0.4" + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/es-errors": { + "node_modules/any-promise": { "version": "1.3.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-module-lexer": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.1.0.tgz", - "integrity": "sha512-n27zTYMjYu1aj4MjCWzSP7G9r75utsaoc8m61weK+W8JMBGGQybd43GstCXZ3WNmSFtGT9wi59qQTW6mhTR5LQ==", - "dev": true, + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", "license": "MIT" }, - "node_modules/es-object-atoms": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": ">= 0.4" + "node": ">= 8" } }, - "node_modules/es-to-primitive": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.4.tgz", - "integrity": "sha512-yPDz7wqpg1/mmHLmS3tcfTfbw5f1eryXvyghYBffGdERwe+mV7ZcWzTR8LR17Kvqt3qfPurjlonmnq3MKXIOXw==", + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", - "dependencies": { - "es-abstract-get": "^1.0.0", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "is-callable": "^1.2.7", - "is-date-object": "^1.1.0", - "is-symbol": "^1.1.1" - }, "engines": { - "node": ">= 0.4" + "node": ">=8.6" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/escalade": { - "version": "3.2.0", + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", "dev": true, "license": "MIT", "engines": { - "node": ">=6" + "node": ">=14" } }, - "node_modules/escape-html": { - "version": "1.0.3", + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, "license": "MIT" }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", "dev": true, "license": "MIT", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" } }, - "node_modules/eslint": { - "version": "10.6.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.6.0.tgz", - "integrity": "sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==", + "node_modules/ast-v8-to-istanbul": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-1.0.5.tgz", + "integrity": "sha512-UPAgKJFSEGMWSDr3LX4tqnAb4f7KGT8O40Tyx8wbYmmZ/yn58lNCm8h3svs3eXgiGd5AXxz8NDOvXWvicq+rJA==", "dev": true, "license": "MIT", - "workspaces": [ - "packages/*" - ], "dependencies": { - "@eslint-community/eslint-utils": "^4.8.0", - "@eslint-community/regexpp": "^4.12.2", - "@eslint/config-array": "^0.23.5", - "@eslint/config-helpers": "^0.6.0", - "@eslint/core": "^1.2.1", - "@eslint/plugin-kit": "^0.7.2", - "@humanfs/node": "^0.16.6", - "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.4.2", - "@types/estree": "^1.0.6", - "ajv": "^6.14.0", - "cross-spawn": "^7.0.6", - "debug": "^4.3.2", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^9.1.2", - "eslint-visitor-keys": "^5.0.1", - "espree": "^11.2.0", - "esquery": "^1.7.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", - "find-up": "^5.0.0", - "glob-parent": "^6.0.2", - "ignore": "^5.2.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "json-stable-stringify-without-jsonify": "^1.0.1", - "minimatch": "^10.2.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.3" - }, - "bin": { - "eslint": "bin/eslint.js" - }, - "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://eslint.org/donate" - }, - "peerDependencies": { - "jiti": "*" - }, - "peerDependenciesMeta": { - "jiti": { - "optional": true - } + "@jridgewell/trace-mapping": "^0.3.31", + "estree-walker": "^3.0.3", + "js-tokens": "^10.0.0" } }, - "node_modules/eslint-config-prettier": { - "version": "10.1.8", + "node_modules/ast-v8-to-istanbul/node_modules/js-tokens": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-10.0.0.tgz", + "integrity": "sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==", "dev": true, - "license": "MIT", - "bin": { - "eslint-config-prettier": "bin/cli.js" - }, - "funding": { - "url": "https://opencollective.com/eslint-config-prettier" - }, - "peerDependencies": { - "eslint": ">=7.0.0" - } + "license": "MIT" }, - "node_modules/eslint-plugin-jsdoc": { - "version": "62.9.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-62.9.0.tgz", - "integrity": "sha512-PY7/X4jrVgoIDncUmITlUqK546Ltmx/Pd4Hdsu4CvSjryQZJI2mEV4vrdMufyTetMiZ5taNSqvK//BTgVUlNkA==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@es-joy/jsdoccomment": "~0.86.0", - "@es-joy/resolve.exports": "1.2.0", - "are-docs-informative": "^0.0.2", - "comment-parser": "1.4.6", - "debug": "^4.4.3", - "escape-string-regexp": "^4.0.0", - "espree": "^11.2.0", - "esquery": "^1.7.0", - "html-entities": "^2.6.0", - "object-deep-merge": "^2.0.0", - "parse-imports-exports": "^0.2.4", - "semver": "^7.7.4", - "spdx-expression-parse": "^4.0.0", - "to-valid-identifier": "^1.0.0" - }, + "node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "license": "MIT", + "optional": true + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0" + "node": ">=8.0.0" } }, - "node_modules/eslint-plugin-jsdoc/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, + "license": "MIT", "engines": { - "node": ">=10" + "node": "18 || 20 || >=22" } }, - "node_modules/eslint-plugin-prefer-arrow-functions": { - "version": "3.10.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-prefer-arrow-functions/-/eslint-plugin-prefer-arrow-functions-3.10.1.tgz", - "integrity": "sha512-wutwnNG0S/wqNhqryS7ee2yX01dkvgFMDvdaXcdEfSGcxz37FRfJUCD0YL2Jur0ANCKocgplF7n6GBPhe+DNNg==", - "dev": true, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", "license": "MIT", - "dependencies": { - "@typescript-eslint/types": "^8.62.1", - "@typescript-eslint/utils": "^8.62.1" - }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24.0.0" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0" + "node": "^4.5.0 || >= 5.9" } }, - "node_modules/eslint-plugin-prefer-arrow-functions/node_modules/@typescript-eslint/project-service": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.65.0.tgz", - "integrity": "sha512-SxnPhbTsGahizDgbu7oqFH/xVtzIqMd/s+WtnSxNxJZJpLbdT5IPdzg8EZxO3+PoKahXmwJLeNQOpKJb3/bi7Q==", + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.65.0", - "@typescript-eslint/types": "^8.65.0", - "debug": "^4.4.3" - }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=8" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/eslint-plugin-prefer-arrow-functions/node_modules/@typescript-eslint/scope-manager": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.65.0.tgz", - "integrity": "sha512-Esbl8OSYiVxBokYgWPf7VVWg/BE798wXhimnn9ML9Pt5qoDf8bfQlgjlKXR/k98+AcNzlLKYrpCcrcuZ9DZLgg==", - "dev": true, + "node_modules/body-parser": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/visitor-keys": "8.65.0" + "bytes": "^3.1.2", + "content-type": "^2.0.0", + "debug": "^4.4.3", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", + "on-finished": "^2.4.1", + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=18" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://opencollective.com/express" } }, - "node_modules/eslint-plugin-prefer-arrow-functions/node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.65.0.tgz", - "integrity": "sha512-j6GzGqCiRdA7Qhur2VVmKZAkBLfnHFQfx4TaJGL9RMveZqCo48jSHHO0DTgizEnGhtWnqmbtCUSrqSkdiY/0Hg==", - "dev": true, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=18" }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "url": "https://opencollective.com/express" } }, - "node_modules/eslint-plugin-prefer-arrow-functions/node_modules/@typescript-eslint/types": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.65.0.tgz", - "integrity": "sha512-JSSwWNy+H0E/01jJEM+hrX6N0OFDzFzeIhHFSAS01tlVaevpG8cFyYRPhS5yjGOvBUx3sqQHVMjCL1CAZZMxBg==", + "node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, "license": "MIT", - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "dependencies": { + "balanced-match": "^4.0.2" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "engines": { + "node": "20 || >=22" } }, - "node_modules/eslint-plugin-prefer-arrow-functions/node_modules/@typescript-eslint/typescript-estree": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.65.0.tgz", - "integrity": "sha512-JboAE2swaYt4tb1fHhHTABE2K+OLy09XfcTbhnk4Pw96f9dd2e9iYsJ28gBggHlo5z5x1rkyWvcPoTuNTd4oGg==", - "dev": true, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.65.0", - "@typescript-eslint/tsconfig-utils": "8.65.0", - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/visitor-keys": "8.65.0", - "debug": "^4.4.3", - "minimatch": "^10.2.2", - "semver": "^7.7.3", - "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.5.0" + "fill-range": "^7.1.1" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "typescript": ">=4.8.4 <6.1.0" + "node": ">=8" } }, - "node_modules/eslint-plugin-prefer-arrow-functions/node_modules/@typescript-eslint/utils": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.65.0.tgz", - "integrity": "sha512-gXiwIHsYreboxeJucHKPvgwl7dXt50mF8s1/c00cP/WoVTyWKFdtfhRWwZiXYFU5H2O8vVoSLNrexFZjYS/SGA==", - "dev": true, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.65.0", - "@typescript-eslint/types": "8.65.0", - "@typescript-eslint/typescript-estree": "8.65.0" - }, + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "license": "MIT" + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "license": "MIT" + }, + "node_modules/buttplug": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/buttplug/-/buttplug-3.2.2.tgz", + "integrity": "sha512-TGkQzG6dxEjuFX29eRoWkh82vsQhGQ+E98tZtN8fWn1NOG7v/9H0FFkNXrpmeRt9FFS0GdHTvubfZ8dcIPGSAA==", + "license": "BSD-3-Clause", + "dependencies": { + "class-transformer": "^0.5.1", + "eventemitter3": "^5.0.1", + "reflect-metadata": "^0.2.1", + "ws": "^8.16.0" + } + }, + "node_modules/buttplug/node_modules/reflect-metadata": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", + "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", + "license": "Apache-2.0" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" - }, - "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.1.0" + "node": ">= 0.8" } }, - "node_modules/eslint-plugin-prefer-arrow-functions/node_modules/@typescript-eslint/visitor-keys": { - "version": "8.65.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.65.0.tgz", - "integrity": "sha512-8C71BQkGjiMmXtop7pHVJu1l2NNShFdkCyD6a2ezzs5vU/L3LRtb69EtcteFwz0mYMPzIgOw0n6OV4VBUWZd7A==", - "dev": true, + "node_modules/call-bind": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", + "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.65.0", - "eslint-visitor-keys": "^5.0.0" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "get-intrinsic": "^1.3.0", + "set-function-length": "^1.2.2" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">= 0.4" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-plugin-prefer-arrow-functions/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" }, "engines": { - "node": ">=10" + "node": ">= 0.4" } }, - "node_modules/eslint-scope": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", - "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", "dependencies": { - "@types/esrecurse": "^4.3.1", - "@types/estree": "^1.0.8", - "esrecurse": "^4.3.0", - "estraverse": "^5.2.0" + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": ">= 0.4" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint-visitor-keys": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", - "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "node_modules/chai": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", + "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", "dev": true, - "license": "Apache-2.0", + "license": "MIT", "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" + "node": ">=18" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", - "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", - "dev": true, + "node_modules/chokidar": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", + "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "readdirp": "^5.0.0" + }, + "engines": { + "node": ">= 20.19.0" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "url": "https://paulmillr.com/funding/" } }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "dev": true, + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", "license": "MIT" }, - "node_modules/espree": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", - "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", "dependencies": { - "acorn": "^8.16.0", - "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^5.0.1" + "color-name": "~1.1.4" }, "engines": { - "node": "^20.19.0 || ^22.13.0 || >=24" - }, - "funding": { - "url": "https://opencollective.com/eslint" + "node": ">=7.0.0" } }, - "node_modules/esquery": { - "version": "1.7.0", + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", "dev": true, - "license": "BSD-3-Clause", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dev": true, + "license": "MIT", "dependencies": { - "estraverse": "^5.1.0" + "delayed-stream": "~1.0.0" }, "engines": { - "node": ">=0.10" + "node": ">= 0.8" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "estraverse": "^5.2.0" - }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "license": "MIT", "engines": { - "node": ">=4.0" + "node": ">= 6" } }, - "node_modules/estraverse": { - "version": "5.3.0", + "node_modules/comment-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.6.tgz", + "integrity": "sha512-ObxuY6vnbWTN6Od72xfwN9DbzC7Y2vv8u1Soi9ahRKL37gb6y1qk6/dgjs+3JWuXJHWvsg3BXIwzd/rkmAwavg==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "engines": { - "node": ">=4.0" + "node": ">= 12.0.0" } }, - "node_modules/estree-walker": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", - "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", "dev": true, "license": "MIT", - "dependencies": { - "@types/estree": "^1.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/esutils": { - "version": "2.0.3", - "dev": true, - "license": "BSD-2-Clause", + "node_modules/content-disposition": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/etag": { - "version": "1.8.1", + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, "license": "MIT" }, - "node_modules/expect-type": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", - "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", - "dev": true, - "license": "Apache-2.0", + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", "engines": { - "node": ">=12.0.0" + "node": ">= 0.6" } }, - "node_modules/express": { - "version": "5.2.1", + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cookiejar": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.4.tgz", + "integrity": "sha512-LDx6oHrK+PhzLKJU9j5S7/Y3jM/mUHvD/DeI1WQmJn652iPC5Y4TBzC9l+5OMOXlyTTA+SmVUPm0HQUwpD5Jqw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" + "object-assign": "^4", + "vary": "^1" }, "engines": { - "node": ">= 18" + "node": ">= 0.10" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, - "node_modules/fast-copy": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.3.tgz", - "integrity": "sha512-58apWr0GUiDFM8+3afrO6eYwJBn9ZAhDOzG3L+/9llab/haCARS2UIfffmOurYLwbgDRs8n0rfr6qAAPEAuAQw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "license": "MIT" - }, - "node_modules/fast-json-stable-stringify": { - "version": "2.1.0", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-levenshtein": { - "version": "2.0.6", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "dev": true, - "license": "MIT" - }, - "node_modules/fast-uri": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.2.tgz", - "integrity": "sha512-rVjf7ArG3LTk+FS6Yw81V1DLuZl1bRbNrev6Tmd/9RaroeeRRJhAt7jg/6YFxbvAQXUCavSoZhPPj6oOx+5KjQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fastify" - }, - { - "type": "opencollective", - "url": "https://opencollective.com/fastify" - } - ], - "license": "BSD-3-Clause" + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } }, - "node_modules/file-entry-cache": { - "version": "8.0.0", + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", "dev": true, "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", "dependencies": { - "flat-cache": "^4.0.0" + "ms": "^2.1.3" }, "engines": { - "node": ">=16.0.0" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" }, "engines": { - "node": ">=8" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/filter-obj": { - "version": "6.1.0", + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "dev": true, "license": "MIT", "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.4.0" } }, - "node_modules/finalhandler": { - "version": "2.1.1", + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, + "asap": "^2.0.0", + "wrappy": "1" + } + }, + "node_modules/dotenv": { + "version": "17.4.2", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.2.tgz", + "integrity": "sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==", + "license": "BSD-2-Clause", "engines": { - "node": ">= 18.0.0" + "node": ">=12" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "url": "https://dotenvx.com" } }, - "node_modules/find-cache-dir": { - "version": "2.1.0", - "dev": true, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", "dependencies": { - "commondir": "^1.0.1", - "make-dir": "^2.0.0", - "pkg-dir": "^3.0.0" + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" }, "engines": { - "node": ">=6" + "node": ">= 0.4" } }, - "node_modules/find-up": { - "version": "5.0.0", - "dev": true, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", - "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" - }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.8" } }, - "node_modules/find-yarn-workspace-root": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", - "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", - "license": "Apache-2.0", + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "dev": true, + "license": "MIT", "dependencies": { - "micromatch": "^4.0.2" + "once": "^1.4.0" } }, - "node_modules/flat-cache": { - "version": "4.0.1", - "dev": true, + "node_modules/engine.io": { + "version": "6.6.9", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.9.tgz", + "integrity": "sha512-clKkw4C7nJ22mGgoVcCg6V/W/TxdNyIOTr89k2ONZu81qqkddPFDF0LXcbAwhzPD8DjkiRCjzuiO6Y+fkpD4vg==", "license": "MIT", "dependencies": { - "flatted": "^3.2.9", - "keyv": "^4.5.4" + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "@types/ws": "^8.5.12", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.21.0" }, "engines": { - "node": ">=16" + "node": ">=10.2.0" } }, - "node_modules/flatted": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", - "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", - "dev": true, - "license": "ISC" - }, - "node_modules/for-each": { - "version": "0.3.5", + "node_modules/engine.io-client": { + "version": "6.6.6", + "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.6.tgz", + "integrity": "sha512-iY6QdftLQ9pyiPoX082bpf/u1UewnOaJrtJIF9T0++QB34lZrj0uP+Q/bj8AlUsAxqhnkTV2BS8SBZSxOmoV5Q==", "dev": true, "license": "MIT", "dependencies": { - "is-callable": "^1.2.7" - }, + "@socket.io/component-emitter": "~3.1.0", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.21.0", + "xmlhttprequest-ssl": "~2.1.1" + } + }, + "node_modules/engine.io-parser": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", + "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", + "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=10.0.0" } }, - "node_modules/form-data": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", - "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", - "dev": true, + "node_modules/engine.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.4", - "mime-types": "^2.1.35" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { - "node": ">= 6" + "node": ">= 0.6" } }, - "node_modules/form-data/node_modules/mime-db": { + "node_modules/engine.io/node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/form-data/node_modules/mime-types": { + "node_modules/engine.io/node_modules/mime-types": { "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -5751,529 +3695,814 @@ "node": ">= 0.6" } }, - "node_modules/formidable": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", - "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", - "dev": true, + "node_modules/engine.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/error-class-utils": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/error-class-utils/-/error-class-utils-4.0.1.tgz", + "integrity": "sha512-0IWumkSpGaaQAyViomL6Dew8F+GqS/kz+rny9krYHxki/irnYIZUZlPvI68HwDzBdZVGHFQA/1OTLl02F9WwuQ==", + "license": "MIT", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/error-custom-class": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/error-custom-class/-/error-custom-class-10.0.1.tgz", + "integrity": "sha512-z+z+23zR3+fDkbHB4cEwuMJdLAmE3P+qqMEAHzz03dMxz+92iY+FaahHEDEuUOoa/z1xVIIZoFD+VAd1XpAV2A==", "license": "MIT", "dependencies": { - "@paralleldrive/cuid2": "^2.2.2", - "dezalgo": "^1.0.4", - "once": "^1.4.0" + "error-class-utils": "^4.0.1" }, "engines": { - "node": ">=14.0.0" - }, - "funding": { - "url": "https://ko-fi.com/tunnckoCore/commissions" + "node": ">=18.18.0" } }, - "node_modules/forwarded": { - "version": "0.2.0", + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.4" } }, - "node_modules/fresh": { - "version": "2.0.0", + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", "engines": { - "node": ">= 0.8" + "node": ">= 0.4" } }, - "node_modules/fs-extra": { - "version": "10.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", - "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "node_modules/es-module-lexer": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.3.1.tgz", + "integrity": "sha512-shc1dbU90Yl/xq1QrC7QRtfcwURZuVRfPhZbDoldJ1cn1gzDvBaBWlv0eFolj5+0znnPJz5TXLxsN77X/12KTA==", + "dev": true, + "license": "MIT" + }, + "node_modules/es-object-atoms": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", + "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", "license": "MIT", "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "es-errors": "^1.3.0" }, "engines": { - "node": ">=12" + "node": ">= 0.4" } }, - "node_modules/fsevents": { - "version": "2.3.3", + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">= 0.4" } }, - "node_modules/function-bind": { - "version": "1.1.2", + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, "license": "MIT", + "engines": { + "node": ">=10" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/function.prototype.name": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.2.0.tgz", - "integrity": "sha512-jObKIik1P2QjPHP5nz5BaOtUlfgS0fWo8IUByNXkM+o+02sJOi94em77GwJKQSJ3gfPHdgzLNrHc1uokV4P/ew==", + "node_modules/eslint": { + "version": "10.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-10.8.0.tgz", + "integrity": "sha512-nuKKvN+oIBO0koN7Tm7dlkmnkc21mtt0QJLwAKzjLq14y6lRTdVG36MZHJ8eQHwdJMwZbQNMlPOYedMq/oVJvQ==", "dev": true, "license": "MIT", + "workspaces": [ + "packages/*" + ], "dependencies": { - "call-bind": "^1.0.9", - "call-bound": "^1.0.4", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2", - "hasown": "^2.0.4", - "is-callable": "^1.2.7", - "is-document.all": "^1.0.0" + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.2", + "@eslint/config-array": "^0.23.5", + "@eslint/config-helpers": "^0.7.0", + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^9.1.2", + "eslint-visitor-keys": "^5.0.1", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "minimatch": "^10.2.5", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" }, "engines": { - "node": ">= 0.4" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, - "node_modules/functions-have-names": { - "version": "1.2.3", + "node_modules/eslint-plugin-jsdoc": { + "version": "62.9.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-62.9.0.tgz", + "integrity": "sha512-PY7/X4jrVgoIDncUmITlUqK546Ltmx/Pd4Hdsu4CvSjryQZJI2mEV4vrdMufyTetMiZ5taNSqvK//BTgVUlNkA==", "dev": true, - "license": "MIT", + "license": "BSD-3-Clause", + "dependencies": { + "@es-joy/jsdoccomment": "~0.86.0", + "@es-joy/resolve.exports": "1.2.0", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.6", + "debug": "^4.4.3", + "escape-string-regexp": "^4.0.0", + "espree": "^11.2.0", + "esquery": "^1.7.0", + "html-entities": "^2.6.0", + "object-deep-merge": "^2.0.0", + "parse-imports-exports": "^0.2.4", + "semver": "^7.7.4", + "spdx-expression-parse": "^4.0.0", + "to-valid-identifier": "^1.0.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-jsdoc/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/eslint" } }, - "node_modules/generator-function": { - "version": "2.0.1", + "node_modules/eslint-plugin-jsdoc/node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, "engines": { - "node": ">= 0.4" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/gensync": { - "version": "1.0.0-beta.2", + "node_modules/eslint-plugin-jsdoc/node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, - "license": "MIT", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">=6.9.0" + "node": ">=10" } }, - "node_modules/get-intrinsic": { - "version": "1.3.0", + "node_modules/eslint-plugin-prefer-arrow-functions": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prefer-arrow-functions/-/eslint-plugin-prefer-arrow-functions-3.10.1.tgz", + "integrity": "sha512-wutwnNG0S/wqNhqryS7ee2yX01dkvgFMDvdaXcdEfSGcxz37FRfJUCD0YL2Jur0ANCKocgplF7n6GBPhe+DNNg==", + "dev": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" + "@typescript-eslint/types": "^8.62.1", + "@typescript-eslint/utils": "^8.62.1" }, "engines": { - "node": ">= 0.4" + "node": "^20.19.0 || ^22.13.0 || >=24.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0" } }, - "node_modules/get-proto": { - "version": "1.0.1", - "license": "MIT", + "node_modules/eslint-scope": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", + "integrity": "sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==", + "dev": true, + "license": "BSD-2-Clause", "dependencies": { - "dunder-proto": "^1.0.1", - "es-object-atoms": "^1.0.0" + "@types/esrecurse": "^4.3.1", + "@types/estree": "^1.0.8", + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" }, "engines": { - "node": ">= 0.4" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/get-symbol-description": { - "version": "1.1.0", + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6" - }, + "license": "Apache-2.0", "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/eslint" } }, - "node_modules/glob-parent": { - "version": "6.0.2", + "node_modules/eslint/node_modules/ajv": { + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz", + "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.3" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", "engines": { - "node": ">=10.13.0" + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/globals": { - "version": "17.7.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz", - "integrity": "sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==", + "node_modules/eslint/node_modules/espree": { + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-11.2.0.tgz", + "integrity": "sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.16.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^5.0.1" + }, "engines": { - "node": ">=18" + "node": "^20.19.0 || ^22.13.0 || >=24" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/eslint" } }, - "node_modules/globalthis": { - "version": "1.0.4", + "node_modules/eslint/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, - "license": "MIT", + "license": "BSD-2-Clause", "dependencies": { - "define-properties": "^1.2.1", - "gopd": "^1.0.1" + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": ">= 0.4" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://opencollective.com/eslint" } }, - "node_modules/gopd": { - "version": "1.2.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=0.10" } }, - "node_modules/graceful-fs": { - "version": "4.2.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", - "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", - "license": "ISC" - }, - "node_modules/has-bigints": { - "version": "1.1.0", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=4.0" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", "engines": { - "node": ">=8" + "node": ">=4.0" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, "license": "MIT", "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "@types/estree": "^1.0.0" } }, - "node_modules/has-proto": { - "version": "1.2.0", + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.0" - }, + "license": "BSD-2-Clause", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/has-symbols": { - "version": "1.1.0", + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.6" } }, - "node_modules/has-tostringtag": { - "version": "1.0.2", + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", "dev": true, - "license": "MIT", - "dependencies": { - "has-symbols": "^1.0.3" - }, + "license": "Apache-2.0", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=12.0.0" } }, - "node_modules/hasown": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", - "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "node_modules/express": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { - "function-bind": "^1.1.2" + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" }, "engines": { - "node": ">= 0.4" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/help-me": { - "version": "5.0.0", + "node_modules/fast-copy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.4.tgz", + "integrity": "sha512-eVAiWVNPSEGIzDl5yPuLrx8fNMogScXvD9xp1Kzd41FjRIz2I3sSIcxsFeM5EzFfHAfobdvs8ZySffUopljvIA==", "dev": true, "license": "MIT" }, - "node_modules/homedir-polyfill": { - "version": "1.0.3", + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true, - "license": "MIT", - "dependencies": { - "parse-passwd": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } + "license": "MIT" }, - "node_modules/html-entities": { - "version": "2.6.0", + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "dev": true, + "license": "MIT" + }, + "node_modules/fast-uri": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", "funding": [ { "type": "github", - "url": "https://github.com/sponsors/mdevils" + "url": "https://github.com/sponsors/fastify" }, { - "type": "patreon", - "url": "https://patreon.com/mdevils" + "type": "opencollective", + "url": "https://opencollective.com/fastify" } ], - "license": "MIT" + "license": "BSD-3-Clause" }, - "node_modules/html-escaper": { - "version": "2.0.2", + "node_modules/fd-package-json": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fd-package-json/-/fd-package-json-2.0.0.tgz", + "integrity": "sha512-jKmm9YtsNXN789RS/0mSzOC1NUq9mkVd65vbSSVsKdjGvYXBuE4oWe2QOEoFeRmJg+lPuZxpmrfFclNhoRMneQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "dependencies": { + "walk-up-path": "^4.0.0" + } }, - "node_modules/http-errors": { - "version": "2.0.1", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, "license": "MIT", "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" + "flat-cache": "^4.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">=16.0.0" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" + "engines": { + "node": ">=8" + } + }, + "node_modules/filter-obj": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-6.1.0.tgz", + "integrity": "sha512-xdMtCAODmPloU9qtmPcdBV9Kd27NtMse+4ayThxqIHUES5Z2S6bGpap5PpdmNM56ub7y3i1eyr+vJJIIgWGKmA==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/iconv-lite": { - "version": "0.7.2", + "node_modules/finalhandler": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 18.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, - "node_modules/ignore": { - "version": "5.3.2", + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", "dev": true, "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, "engines": { - "node": ">= 4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "dev": true, - "license": "ISC" + "node_modules/find-yarn-workspace-root": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/find-yarn-workspace-root/-/find-yarn-workspace-root-2.0.0.tgz", + "integrity": "sha512-1IMnbjt4KzsQfnhnzNd8wUEgXZ44IzZaZmnLYx7D5FZlaHt2gW20Cri8Q+E/t5tIj4+epTBub+2Zxu/vNILzqQ==", + "license": "Apache-2.0", + "dependencies": { + "micromatch": "^4.0.2" + } }, - "node_modules/imurmurhash": { - "version": "0.1.4", + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, "engines": { - "node": ">=0.8.19" + "node": ">=16" } }, - "node_modules/inherits": { - "version": "2.0.4", + "node_modules/flatted": { + "version": "3.4.4", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.4.tgz", + "integrity": "sha512-5+ybhBZANEJxaH3X5evAFatUxLfEHSr7n6kYJ+1Qd0mUqr4eu9gIf6GDbWHf8RJijHrjjO8G+la14SlL2SeS1Q==", + "dev": true, "license": "ISC" }, - "node_modules/internal-slot": { - "version": "1.1.0", + "node_modules/form-data": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "dev": true, "license": "MIT", "dependencies": { - "es-errors": "^1.3.0", - "hasown": "^2.0.2", - "side-channel": "^1.1.0" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { - "node": ">= 0.4" + "node": ">= 6" } }, - "node_modules/ipaddr.js": { - "version": "1.9.1", + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, "license": "MIT", "engines": { - "node": ">= 0.10" + "node": ">= 0.6" } }, - "node_modules/is-array-buffer": { - "version": "3.0.5", + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, "license": "MIT", "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" + "mime-db": "1.52.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.6" } }, - "node_modules/is-async-function": { - "version": "2.1.1", + "node_modules/formatly": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/formatly/-/formatly-0.3.0.tgz", + "integrity": "sha512-9XNj/o4wrRFyhSMJOvsuyMwy8aUfBaZ1VrqHVfohyXf0Sw0e+yfKG+xZaY3arGCOMdwFsqObtzVOc1gU9KiT9w==", "dev": true, "license": "MIT", "dependencies": { - "async-function": "^1.0.0", - "call-bound": "^1.0.3", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" + "fd-package-json": "^2.0.0" }, - "engines": { - "node": ">= 0.4" + "bin": { + "formatly": "bin/index.mjs" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=18.3.0" } }, - "node_modules/is-bigint": { - "version": "1.1.0", + "node_modules/formidable": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", + "integrity": "sha512-YikH+7CUTOtP44ZTnUhR7Ic2UASBPOqmaRkRKxRbywPTe5VxF7RRCck4af9wutiZ/QKM5nME9Bie2fFaPz5Gug==", "dev": true, "license": "MIT", "dependencies": { - "has-bigints": "^1.0.2" + "@paralleldrive/cuid2": "^2.2.2", + "dezalgo": "^1.0.4", + "once": "^1.4.0" }, "engines": { - "node": ">= 0.4" + "node": ">=14.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://ko-fi.com/tunnckoCore/commissions" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/is-boolean-object": { - "version": "1.2.2", - "dev": true, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=12" } }, - "node_modules/is-callable": { - "version": "1.2.7", + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", "dev": true, + "hasInstallScript": true, "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/is-core-module": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", - "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", - "dev": true, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", - "dependencies": { - "hasown": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, "funding": { "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-data-view": { - "version": "1.0.2", - "dev": true, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "get-intrinsic": "^1.2.6", - "is-typed-array": "^1.1.13" + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" }, "engines": { "node": ">= 0.4" @@ -6282,74 +4511,63 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-date-object": { - "version": "1.1.0", - "dev": true, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" }, "engines": { "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", + "node_modules/get-tsconfig": { + "version": "4.14.0", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.14.0.tgz", + "integrity": "sha512-yTb+8DXzDREzgvYmh6s9vHsSVCHeC0G3PI5bEXNBHtmshPnO+S5O7qgLEOn0I5QvMy6kpZN8K1NKGyilLb93wA==", + "dev": true, "license": "MIT", - "bin": { - "is-docker": "cli.js" - }, - "engines": { - "node": ">=8" + "dependencies": { + "resolve-pkg-maps": "^1.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/is-document.all": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-document.all/-/is-document.all-1.0.0.tgz", - "integrity": "sha512-+XSoyS05OdBbhFuELhgTCpFNHkpBOJqtsZfUFFpe5QTw+9Sjbh8zitxhQkYAo6wV7e1Vb8cAPvpCk9jGam/82g==", + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, - "license": "MIT", + "license": "ISC", "dependencies": { - "call-bound": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" + "is-glob": "^4.0.3" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-error-instance": { - "version": "3.0.1", - "license": "MIT", "engines": { - "node": ">=18.18.0" + "node": ">=10.13.0" } }, - "node_modules/is-extglob": { - "version": "2.1.1", + "node_modules/globals": { + "version": "17.9.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.9.0.tgz", + "integrity": "sha512-m/MvAW61QVU5VDNF1Vj8axt016h8w7L5TU1e9zlab7XIttAT2YAlCwl75K1fOqvMM9apmD7lbCIRhpfkhmxhCg==", "dev": true, "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-finalizationregistry": { - "version": "1.1.1", - "dev": true, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, "engines": { "node": ">= 0.4" }, @@ -6357,38 +4575,37 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-generator-function": { - "version": "1.1.2", - "dev": true, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "license": "MIT", - "dependencies": { - "call-bound": "^1.0.4", - "generator-function": "^2.0.0", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/is-glob": { - "version": "4.0.3", - "dev": true, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "es-define-property": "^1.0.0" }, - "engines": { - "node": ">=0.10.0" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-map": { - "version": "2.0.3", - "dev": true, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -6397,10 +4614,15 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-negative-zero": { - "version": "2.0.3", + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, "engines": { "node": ">= 0.4" }, @@ -6408,182 +4630,214 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, "engines": { - "node": ">=0.12.0" + "node": ">= 0.4" } }, - "node_modules/is-number-object": { - "version": "1.1.1", + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-entities": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/html-entities/-/html-entities-2.6.0.tgz", + "integrity": "sha512-kig+rMn/QOVRvr7c86gQ8lWXq+Hkv6CbAH1hLu+RG338StTpE8Z0b44SDVaqVu7HGKf27frdmUYEs9hTUX/cLQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/mdevils" + }, + { + "type": "patreon", + "url": "https://patreon.com/mdevils" + } + ], + "license": "MIT" + }, + "node_modules/html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true, + "license": "MIT" + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" }, "engines": { - "node": ">= 0.4" + "node": ">= 0.8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/is-plain-obj": { - "version": "4.1.0", + "node_modules/iconv-lite": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", + "integrity": "sha512-IKXpvIzjnC9XTAUbVBcMfGS0EPaIXtW6v+zr+RRp+hqULEpo0owZax6wyRwPOJbWbzjYspQwusTsfVr0ifh4uQ==", "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, "engines": { - "node": ">=12" + "node": ">=0.10.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/is-plain-object": { - "version": "2.0.4", + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", - "dependencies": { - "isobject": "^3.0.1" - }, "engines": { - "node": ">=0.10.0" + "node": ">= 4" } }, - "node_modules/is-promise": { - "version": "4.0.0", - "license": "MIT" + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" }, - "node_modules/is-regex": { - "version": "1.2.1", + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.8.19" } }, - "node_modules/is-set": { - "version": "2.0.3", - "dev": true, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "license": "MIT", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.10" } }, - "node_modules/is-shared-array-buffer": { - "version": "1.0.4", + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", "dev": true, "license": "MIT", "dependencies": { - "call-bound": "^1.0.3" + "binary-extensions": "^2.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=8" } }, - "node_modules/is-string": { - "version": "1.1.1", - "dev": true, + "node_modules/is-docker": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", + "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-tostringtag": "^1.0.2" + "bin": { + "is-docker": "cli.js" }, "engines": { - "node": ">= 0.4" + "node": ">=8" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-symbol": { - "version": "1.1.1", - "dev": true, + "node_modules/is-error-instance": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-error-instance/-/is-error-instance-3.0.1.tgz", + "integrity": "sha512-nEw1ptjtbmXRkHOFrLvpHywsv+jMUZdtqI2BAC/XxMd24vUY5gXhvOprH0Bh4xA2gqQCQsEkr7aaxsgvxYRjnQ==", "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-symbols": "^1.1.0", - "safe-regex-test": "^1.1.0" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=18.18.0" } }, - "node_modules/is-typed-array": { - "version": "1.1.15", + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "dev": true, "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/is-weakmap": { - "version": "2.0.2", + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 0.4" + "dependencies": { + "is-extglob": "^2.1.1" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/is-weakref": { - "version": "1.1.1", - "dev": true, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.12.0" } }, - "node_modules/is-weakset": { - "version": "2.0.4", - "dev": true, + "node_modules/is-plain-obj": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", + "integrity": "sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==", "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "get-intrinsic": "^1.2.6" - }, "engines": { - "node": ">= 0.4" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, "node_modules/is-wsl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", @@ -6608,14 +4862,6 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "license": "ISC" }, - "node_modules/isobject": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/isolated-vm": { "version": "6.1.2", "resolved": "https://registry.npmjs.org/isolated-vm/-/isolated-vm-6.1.2.tgz", @@ -6631,6 +4877,8 @@ }, "node_modules/istanbul-lib-coverage": { "version": "3.2.2", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz", + "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -6639,6 +4887,8 @@ }, "node_modules/istanbul-lib-report": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz", + "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -6650,35 +4900,10 @@ "node": ">=10" } }, - "node_modules/istanbul-lib-report/node_modules/make-dir": { - "version": "4.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/istanbul-lib-report/node_modules/semver": { - "version": "7.8.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", - "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/istanbul-reports": { "version": "3.2.0", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", + "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -6701,45 +4926,35 @@ }, "node_modules/joycon": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", "dev": true, "license": "MIT", "engines": { "node": ">=10" } }, - "node_modules/js-tokens": { - "version": "4.0.0", - "dev": true, - "license": "MIT" - }, - "node_modules/jsdoc-type-pratt-parser": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-7.2.0.tgz", - "integrity": "sha512-dh140MMgjyg3JhJZY/+iEzW+NO5xR2gpbDFKHqotCmexElVntw7GjWjt511+C/Ef02RU5TKYrJo/Xlzk+OLaTw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=20.0.0" - } - }, - "node_modules/jsesc": { - "version": "3.1.0", + "node_modules/jsdoc-type-pratt-parser": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-7.2.0.tgz", + "integrity": "sha512-dh140MMgjyg3JhJZY/+iEzW+NO5xR2gpbDFKHqotCmexElVntw7GjWjt511+C/Ef02RU5TKYrJo/Xlzk+OLaTw==", "dev": true, "license": "MIT", - "bin": { - "jsesc": "bin/jsesc" - }, "engines": { - "node": ">=6" + "node": ">=20.0.0" } }, "node_modules/json-buffer": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", "dev": true, "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "license": "MIT" }, "node_modules/json-stable-stringify": { @@ -6763,20 +4978,11 @@ }, "node_modules/json-stable-stringify-without-jsonify": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", "dev": true, "license": "MIT" }, - "node_modules/json5": { - "version": "2.2.3", - "dev": true, - "license": "MIT", - "bin": { - "json5": "lib/cli.js" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/jsonfile": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.1.tgz", @@ -6800,20 +5006,14 @@ }, "node_modules/keyv": { "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", "dev": true, "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } }, - "node_modules/kind-of": { - "version": "6.0.3", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/klaw-sync": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/klaw-sync/-/klaw-sync-6.0.0.tgz", @@ -6823,8 +5023,49 @@ "graceful-fs": "^4.1.11" } }, + "node_modules/knip": { + "version": "6.31.0", + "resolved": "https://registry.npmjs.org/knip/-/knip-6.31.0.tgz", + "integrity": "sha512-NbeIEmUS2VUMjAkbiSNOKPJeV9wpCsr0660sUyKyMQbk4Iom0++nTLInVp4MJ+LfR4kORnw67bDi5tvO7YLnzA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/webpro" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/knip" + } + ], + "license": "ISC", + "dependencies": { + "fdir": "^6.5.0", + "formatly": "^0.3.0", + "get-tsconfig": "4.14.0", + "jiti": "^2.7.0", + "oxc-parser": "^0.142.0", + "oxc-resolver": "11.24.2", + "picomatch": "^4.0.5", + "smol-toml": "^1.7.1", + "strip-json-comments": "5.0.3", + "tinyglobby": "^0.2.17", + "unbash": "^4.0.4", + "yaml": "^2.9.0", + "zod": "^4.4.3" + }, + "bin": { + "knip": "bin/knip.js", + "knip-bun": "bin/knip-bun.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, "node_modules/levn": { "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", "dev": true, "license": "MIT", "dependencies": { @@ -6836,9 +5077,9 @@ } }, "node_modules/lightningcss": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.33.0.tgz", + "integrity": "sha512-WkUDrojuJs0xkgGf2udWxa3yGBRxPtxUkB79i6aCZLRgc7PM8fZe9TosfPDcvEpQZbuFASnHYmRLBLUbmLOIIA==", "dev": true, "license": "MPL-2.0", "dependencies": { @@ -6852,23 +5093,23 @@ "url": "https://opencollective.com/parcel" }, "optionalDependencies": { - "lightningcss-android-arm64": "1.32.0", - "lightningcss-darwin-arm64": "1.32.0", - "lightningcss-darwin-x64": "1.32.0", - "lightningcss-freebsd-x64": "1.32.0", - "lightningcss-linux-arm-gnueabihf": "1.32.0", - "lightningcss-linux-arm64-gnu": "1.32.0", - "lightningcss-linux-arm64-musl": "1.32.0", - "lightningcss-linux-x64-gnu": "1.32.0", - "lightningcss-linux-x64-musl": "1.32.0", - "lightningcss-win32-arm64-msvc": "1.32.0", - "lightningcss-win32-x64-msvc": "1.32.0" + "lightningcss-android-arm64": "1.33.0", + "lightningcss-darwin-arm64": "1.33.0", + "lightningcss-darwin-x64": "1.33.0", + "lightningcss-freebsd-x64": "1.33.0", + "lightningcss-linux-arm-gnueabihf": "1.33.0", + "lightningcss-linux-arm64-gnu": "1.33.0", + "lightningcss-linux-arm64-musl": "1.33.0", + "lightningcss-linux-x64-gnu": "1.33.0", + "lightningcss-linux-x64-musl": "1.33.0", + "lightningcss-win32-arm64-msvc": "1.33.0", + "lightningcss-win32-x64-msvc": "1.33.0" } }, "node_modules/lightningcss-android-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", - "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.33.0.tgz", + "integrity": "sha512-gEpRTalKdosp4Bb8qWtc2iOgE5SeIHlpS1up9bFq2wAyYhl1UdTObYiHe98zEM9SQvSoqQZ1IQD0JNpg3Ml5pg==", "cpu": [ "arm64" ], @@ -6887,9 +5128,9 @@ } }, "node_modules/lightningcss-darwin-arm64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.33.0.tgz", + "integrity": "sha512-Sciaz8eenNTKn9b3t7+xr0ipTp9YxKQY4npwQ3mrRuL0BAVHBLyZxofhaKBAVtzmtRZ/zTyo0/to4B1uWG/Djg==", "cpu": [ "arm64" ], @@ -6908,9 +5149,9 @@ } }, "node_modules/lightningcss-darwin-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", - "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.33.0.tgz", + "integrity": "sha512-Z5UPAxzrjlWNNyGy6i65cJzzvgJ5D3T6wMvs+gWpY9d7qRhANrxqAp6LhxIgZhWEw18RfJTGcRxjuLIBr+m8XQ==", "cpu": [ "x64" ], @@ -6929,9 +5170,9 @@ } }, "node_modules/lightningcss-freebsd-x64": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", - "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.33.0.tgz", + "integrity": "sha512-QQM/Ti/hQajJwCY+RiWuCZ9sdtI/XQk7nDK5vC8kkdwixezOlDgvDx7+RT+QjK6FcFT4MpsuoBnHIo/O3StRRg==", "cpu": [ "x64" ], @@ -6950,9 +5191,9 @@ } }, "node_modules/lightningcss-linux-arm-gnueabihf": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", - "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.33.0.tgz", + "integrity": "sha512-N7FVBe6iS24MlM6R/4RBTxGhQheZGs7tiQ9U32UtF75NzP5Q7xWPRqLBCKxlRQRk3rY1jCIPLzx7WzOhuUIRLQ==", "cpu": [ "arm" ], @@ -6971,9 +5212,9 @@ } }, "node_modules/lightningcss-linux-arm64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", - "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.33.0.tgz", + "integrity": "sha512-j2v/itmy4HlNxlc6voKXYgBqNi0Ng2LShg4z7GufpEgs05P+2suBVyi9I6YHq5uoVFx9ETin3eCEhLVyXGQnKg==", "cpu": [ "arm64" ], @@ -6995,9 +5236,9 @@ } }, "node_modules/lightningcss-linux-arm64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", - "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.33.0.tgz", + "integrity": "sha512-yiO5ROMuYQgXbC60yjZU5CYSFZGKXL0HFATXt9mHJn1+zW55oCtMI9NfcVhYLMFDL7gV7oBPon/EmMMGg2OvtQ==", "cpu": [ "arm64" ], @@ -7019,9 +5260,9 @@ } }, "node_modules/lightningcss-linux-x64-gnu": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", - "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.33.0.tgz", + "integrity": "sha512-ar+Ju7LmcN0Jo4FpL4hpFybwNG9/3A/Br5KW2n2jyODg3MEZXaDYADdemoNS+BDNfMgKvylJLj4S5tyRActuAg==", "cpu": [ "x64" ], @@ -7043,9 +5284,9 @@ } }, "node_modules/lightningcss-linux-x64-musl": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", - "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.33.0.tgz", + "integrity": "sha512-RYiYbkokw0trfKqqzfF55lginwEPrD3OJDfTuJzFs1MK6iFnDenaz1fqLLtX4ITG3OktJQXOeTaw1awrBAlZPw==", "cpu": [ "x64" ], @@ -7067,9 +5308,9 @@ } }, "node_modules/lightningcss-win32-arm64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", - "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.33.0.tgz", + "integrity": "sha512-1K+MPfLSFVpphzpdbfkhlWk6wBrTObBzS2T6db10PNOZgR9GoVsAWzwNyuhUYYbTp23j+4RrncfujZ4uAzXvwA==", "cpu": [ "arm64" ], @@ -7088,9 +5329,9 @@ } }, "node_modules/lightningcss-win32-x64-msvc": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", - "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "version": "1.33.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.33.0.tgz", + "integrity": "sha512-OlEICDx/Xl0FqSp4bry8zFnCvGpig3Gl4gCquvYwHuqJKEC1+n9NgDniFvqHGmMv1ZkqDJrDqKKSykTDX+ehuA==", "cpu": [ "x64" ], @@ -7116,6 +5357,8 @@ }, "node_modules/locate-path": { "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", "dev": true, "license": "MIT", "dependencies": { @@ -7128,19 +5371,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/lodash.debounce": { - "version": "4.0.8", - "dev": true, - "license": "MIT" - }, - "node_modules/lru-cache": { - "version": "5.1.1", - "dev": true, - "license": "ISC", - "dependencies": { - "yallist": "^3.0.2" - } - }, "node_modules/magic-string": { "version": "0.30.21", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", @@ -7152,53 +5382,72 @@ } }, "node_modules/magicast": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.3.tgz", - "integrity": "sha512-pVKE4UdSQ7DvHzivsCIFx2BJn1mHG6KsyrFcaxFx6tONdneEuThrDx0Cj3AMg58KyN4pzYT+LHOotxDQDjNvkw==", + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.5.4.tgz", + "integrity": "sha512-llBEhWm1SacoRwgHUoQJYtwp4PBLF4faQi5TCpIGyGs9n4y5+juI0tDgyKIfpqxckRHaHzouUEph3THklWh03w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.3", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "source-map-js": "^1.2.1" } }, "node_modules/make-dir": { - "version": "2.1.0", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz", + "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==", "dev": true, "license": "MIT", "dependencies": { - "pify": "^4.0.1", - "semver": "^5.6.0" + "semver": "^7.5.3" }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/make-dir/node_modules/semver": { - "version": "5.7.2", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "dev": true, "license": "ISC", "bin": { - "semver": "bin/semver" + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/math-intrinsics": { "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", "engines": { "node": ">= 0.4" } }, "node_modules/media-typer": { - "version": "1.1.0", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.1.tgz", + "integrity": "sha512-yz3xRaG20c6/BOzvYoDaGtPmGscs7YivItZEEqe6GbwNfHuxu9YNmvnEkMzKldAGY4/80pRcQRZSEnhquk9XuQ==", "license": "MIT", "engines": { "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/merge-descriptors": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", "engines": { "node": ">=18" @@ -7209,6 +5458,8 @@ }, "node_modules/merge-error-cause": { "version": "5.0.2", + "resolved": "https://registry.npmjs.org/merge-error-cause/-/merge-error-cause-5.0.2.tgz", + "integrity": "sha512-pToOGIivcb/Vz4lJ3faWGbS92+41YW9QOESpuValOci1xP5hT6bQMiw/Pse7PHYsKSPusNT7zMFwDgqzt2lHXw==", "license": "MIT", "dependencies": { "normalize-exception": "^4.0.1", @@ -7243,6 +5494,18 @@ "node": ">=8.6" } }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mime": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", @@ -7258,6 +5521,8 @@ }, "node_modules/mime-db": { "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -7265,6 +5530,8 @@ }, "node_modules/mime-types": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" @@ -7278,13 +5545,13 @@ } }, "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^5.0.5" + "brace-expansion": "^5.0.8" }, "engines": { "node": "18 || 20 || >=22" @@ -7304,6 +5571,8 @@ }, "node_modules/modern-errors": { "version": "7.1.4", + "resolved": "https://registry.npmjs.org/modern-errors/-/modern-errors-7.1.4.tgz", + "integrity": "sha512-FdC4DgT6ewjJN1TqsC8GQ3vmnbbaPiYpZ4BHk6eNigFm5btQMBIKZcNVbH8/nbzVlf+qnARa5l0OqhzP5q2MUA==", "license": "MIT", "dependencies": { "error-class-utils": "^4.0.1", @@ -7323,10 +5592,14 @@ }, "node_modules/ms": { "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "license": "MIT" }, "node_modules/mz": { "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", "license": "MIT", "dependencies": { "any-promise": "^1.0.0", @@ -7335,9 +5608,9 @@ } }, "node_modules/nanoid": { - "version": "3.3.15", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.15.tgz", - "integrity": "sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==", + "version": "3.3.17", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.17.tgz", + "integrity": "sha512-xQLf0A3HOMlgHq0n247/LRuAOYmB7dXJ/DvAxGvsSBij45XtBSmQycu+F8ODbHwns/XyFZagyL1+J0Offw1E0g==", "dev": true, "funding": [ { @@ -7355,44 +5628,33 @@ }, "node_modules/natural-compare": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true, "license": "MIT" }, "node_modules/negotiator": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/node-addon-api": { - "version": "8.9.0", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.9.0.tgz", - "integrity": "sha512-ekZMeaaIzSQTSpr7X2X3iJM7lTzgnx8ahAG9pJfT/7+14mlEM8ZYQ9cgCDvSSRbReFK0oHli3WrZdCiRsgAT9Q==", + "version": "8.9.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.9.1.tgz", + "integrity": "sha512-4eUQWVPCUUUiBjLnHS3cXWeC6ryoPUc0U3rP7IuzapoGbzMqd/r6KKO0clr0b+snQhsrueFEhCZDdK+LK7hxKg==", "license": "MIT", "engines": { "node": "^18 || ^20 || >= 21" } }, - "node_modules/node-environment-flags": { - "version": "1.0.6", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "object.getownpropertydescriptors": "^2.0.3", - "semver": "^5.7.0" - } - }, - "node_modules/node-environment-flags/node_modules/semver": { - "version": "5.7.2", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver" - } - }, "node_modules/node-gyp-build": { "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", "license": "MIT", "bin": { "node-gyp-build": "bin.js", @@ -7401,24 +5663,14 @@ } }, "node_modules/node-os-utils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/node-os-utils/-/node-os-utils-2.0.3.tgz", - "integrity": "sha512-NqItOJn+5355jYz/fYfs4PrMNTbKNG01ILws0rZhEZ3by3a0Kg9hGmzTqVOZnXsh9uww+AHUc4d6A1jEHR6u8g==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/node-os-utils/-/node-os-utils-2.0.4.tgz", + "integrity": "sha512-tywMkTGbyBKG/woIy++33CCtdXwpVRJMMfgbk83DkvZhN7/+BABl8oz1hRE7pOH8DAWoZGKB27bqipQtfFCe0g==", "license": "MIT", "engines": { "node": ">=18.0.0" } }, - "node_modules/node-releases": { - "version": "2.0.50", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.50.tgz", - "integrity": "sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - } - }, "node_modules/nodemon": { "version": "3.1.14", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", @@ -7488,12 +5740,27 @@ }, "node_modules/nodemon/node_modules/has-flag": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, "license": "MIT", "engines": { "node": ">=4" } }, + "node_modules/nodemon/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/nodemon/node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -7522,6 +5789,8 @@ }, "node_modules/nodemon/node_modules/supports-color": { "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, "license": "MIT", "dependencies": { @@ -7533,6 +5802,8 @@ }, "node_modules/normalize-exception": { "version": "4.0.1", + "resolved": "https://registry.npmjs.org/normalize-exception/-/normalize-exception-4.0.1.tgz", + "integrity": "sha512-2fUwKMZLYvHIHZN4a6Bt2FYEea6mDwQcTVCffRvQ1bJHndQRRrNhq8+WQiLolOzJ7bJl59SCURGSwCLvQv/X7w==", "license": "MIT", "dependencies": { "is-error-instance": "^3.0.1", @@ -7554,6 +5825,8 @@ }, "node_modules/object-assign": { "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -7568,6 +5841,8 @@ }, "node_modules/object-inspect": { "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -7585,49 +5860,10 @@ "node": ">= 0.4" } }, - "node_modules/object.assign": { - "version": "4.1.7", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "call-bound": "^1.0.3", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0", - "has-symbols": "^1.1.0", - "object-keys": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/object.getownpropertydescriptors": { - "version": "2.1.9", - "dev": true, - "license": "MIT", - "dependencies": { - "array.prototype.reduce": "^1.0.8", - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.0", - "es-object-atoms": "^1.1.1", - "gopd": "^1.2.0", - "safe-array-concat": "^1.1.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/obug": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.3.tgz", - "integrity": "sha512-9miFgM2OFba7hB+pRgvtV84pYTBaoTHohvmIgiRt6dRIzbwEOIaNaP+dIlGs2fNFoB0SeISs0Jz5WFVRid6Xyg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", + "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", @@ -7640,6 +5876,8 @@ }, "node_modules/on-change": { "version": "4.0.2", + "resolved": "https://registry.npmjs.org/on-change/-/on-change-4.0.2.tgz", + "integrity": "sha512-cMtCyuJmTx/bg2HCpHo3ZLeF7FZnBOapLqZHr2AlLeJ5Ul0Zu2mUJJz051Fdwu/Et2YW04ZD+TtU+gVy0ACNCA==", "license": "MIT", "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" @@ -7650,6 +5888,8 @@ }, "node_modules/on-exit-leak-free": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", "license": "MIT", "engines": { "node": ">=14.0.0" @@ -7657,6 +5897,8 @@ }, "node_modules/on-finished": { "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "license": "MIT", "dependencies": { "ee-first": "1.1.1" @@ -7667,6 +5909,8 @@ }, "node_modules/once": { "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", "dependencies": { "wrappy": "1" @@ -7674,6 +5918,8 @@ }, "node_modules/one-time": { "version": "0.0.4", + "resolved": "https://registry.npmjs.org/one-time/-/one-time-0.0.4.tgz", + "integrity": "sha512-qAMrwuk2xLEutlASoiPiAMW3EN3K96Ka/ilSXYr6qR1zSVXw2j7+yDSqGTC4T9apfLYxM3tLLjKvgPdAUK7kYQ==", "license": "MIT" }, "node_modules/open": { @@ -7694,6 +5940,8 @@ }, "node_modules/optionator": { "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", "dev": true, "license": "MIT", "dependencies": { @@ -7708,24 +5956,79 @@ "node": ">= 0.8.0" } }, - "node_modules/own-keys": { - "version": "1.0.1", + "node_modules/oxc-parser": { + "version": "0.142.0", + "resolved": "https://registry.npmjs.org/oxc-parser/-/oxc-parser-0.142.0.tgz", + "integrity": "sha512-kKR+jPiRJYJDexVoziIg/FVGvr1fT1FZSSJOk6tVoMKKSlsf1Cso+cgGCJkOEDWOP174vRntCPFKg+AS7InWvw==", "dev": true, "license": "MIT", "dependencies": { - "get-intrinsic": "^1.2.6", - "object-keys": "^1.1.1", - "safe-push-apply": "^1.0.0" + "@oxc-project/types": "^0.142.0" }, "engines": { - "node": ">= 0.4" + "node": "^20.19.0 || >=22.12.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-parser/binding-android-arm-eabi": "0.142.0", + "@oxc-parser/binding-android-arm64": "0.142.0", + "@oxc-parser/binding-darwin-arm64": "0.142.0", + "@oxc-parser/binding-darwin-x64": "0.142.0", + "@oxc-parser/binding-freebsd-x64": "0.142.0", + "@oxc-parser/binding-linux-arm-gnueabihf": "0.142.0", + "@oxc-parser/binding-linux-arm-musleabihf": "0.142.0", + "@oxc-parser/binding-linux-arm64-gnu": "0.142.0", + "@oxc-parser/binding-linux-arm64-musl": "0.142.0", + "@oxc-parser/binding-linux-ppc64-gnu": "0.142.0", + "@oxc-parser/binding-linux-riscv64-gnu": "0.142.0", + "@oxc-parser/binding-linux-riscv64-musl": "0.142.0", + "@oxc-parser/binding-linux-s390x-gnu": "0.142.0", + "@oxc-parser/binding-linux-x64-gnu": "0.142.0", + "@oxc-parser/binding-linux-x64-musl": "0.142.0", + "@oxc-parser/binding-openharmony-arm64": "0.142.0", + "@oxc-parser/binding-wasm32-wasi": "0.142.0", + "@oxc-parser/binding-win32-arm64-msvc": "0.142.0", + "@oxc-parser/binding-win32-ia32-msvc": "0.142.0", + "@oxc-parser/binding-win32-x64-msvc": "0.142.0" + } + }, + "node_modules/oxc-resolver": { + "version": "11.24.2", + "resolved": "https://registry.npmjs.org/oxc-resolver/-/oxc-resolver-11.24.2.tgz", + "integrity": "sha512-FY91FiDBj7ls5MsFS9jN3tjz2o0/zsdSsymlakySaBwVJZorHhkWyICLZMKxlu1R9vYo+sd3z1jwb4J8x7bNDw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" + }, + "optionalDependencies": { + "@oxc-resolver/binding-android-arm-eabi": "11.24.2", + "@oxc-resolver/binding-android-arm64": "11.24.2", + "@oxc-resolver/binding-darwin-arm64": "11.24.2", + "@oxc-resolver/binding-darwin-x64": "11.24.2", + "@oxc-resolver/binding-freebsd-x64": "11.24.2", + "@oxc-resolver/binding-linux-arm-gnueabihf": "11.24.2", + "@oxc-resolver/binding-linux-arm-musleabihf": "11.24.2", + "@oxc-resolver/binding-linux-arm64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-arm64-musl": "11.24.2", + "@oxc-resolver/binding-linux-ppc64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-riscv64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-riscv64-musl": "11.24.2", + "@oxc-resolver/binding-linux-s390x-gnu": "11.24.2", + "@oxc-resolver/binding-linux-x64-gnu": "11.24.2", + "@oxc-resolver/binding-linux-x64-musl": "11.24.2", + "@oxc-resolver/binding-openharmony-arm64": "11.24.2", + "@oxc-resolver/binding-wasm32-wasi": "11.24.2", + "@oxc-resolver/binding-win32-arm64-msvc": "11.24.2", + "@oxc-resolver/binding-win32-x64-msvc": "11.24.2" } }, "node_modules/p-limit": { "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "license": "MIT", "dependencies": { @@ -7740,6 +6043,8 @@ }, "node_modules/p-locate": { "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", "dev": true, "license": "MIT", "dependencies": { @@ -7752,37 +6057,27 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/p-try": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/parse-imports-exports": { "version": "0.2.4", + "resolved": "https://registry.npmjs.org/parse-imports-exports/-/parse-imports-exports-0.2.4.tgz", + "integrity": "sha512-4s6vd6dx1AotCx/RCI2m7t7GCh5bDRUtGNvRfHSP2wbBQdMi67pPe7mtzmgwcaQ8VKK/6IB7Glfyu3qdZJPybQ==", "dev": true, "license": "MIT", "dependencies": { "parse-statements": "1.0.11" } }, - "node_modules/parse-passwd": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/parse-statements": { "version": "1.0.11", + "resolved": "https://registry.npmjs.org/parse-statements/-/parse-statements-1.0.11.tgz", + "integrity": "sha512-HlsyYdMBnbPQ9Jr/VgJ1YF4scnldvJpJxCVx6KgqPL4dxppsWrJHCIIxQXMJrqGnsRkNPATbeMJ8Yxu7JMsYcA==", "dev": true, "license": "MIT" }, "node_modules/parseurl": { "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "license": "MIT", "engines": { "node": ">= 0.8" @@ -7831,6 +6126,8 @@ }, "node_modules/path-exists": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", "dev": true, "license": "MIT", "engines": { @@ -7846,11 +6143,6 @@ "node": ">=8" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "dev": true, - "license": "MIT" - }, "node_modules/path-to-regexp": { "version": "8.4.2", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", @@ -7870,29 +6162,23 @@ }, "node_modules/picocolors": { "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "dev": true, "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/pify": { - "version": "4.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/pino": { "version": "10.3.1", "resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz", @@ -7917,6 +6203,8 @@ }, "node_modules/pino-abstract-transport": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz", + "integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==", "license": "MIT", "dependencies": { "split2": "^4.0.0" @@ -7951,96 +6239,21 @@ "version": "7.1.0", "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz", "integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==", - "license": "MIT" - }, - "node_modules/pirates": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", - "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", - "license": "MIT", - "engines": { - "node": ">= 6" - } - }, - "node_modules/pkg-dir": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "find-up": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/find-up": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "locate-path": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/locate-path": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/p-limit": { - "version": "2.3.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/pkg-dir/node_modules/p-locate": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "p-limit": "^2.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/pkg-dir/node_modules/path-exists": { - "version": "3.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } + "license": "MIT" }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "dev": true, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", "license": "MIT", "engines": { - "node": ">= 0.4" + "node": ">= 6" } }, "node_modules/postcss": { - "version": "8.5.15", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz", - "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==", + "version": "8.5.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.25.tgz", + "integrity": "sha512-DTPx3RWSSnWyzLxQnlH0rJP+EW5ekl16ZU4/psbIhA0e53kJfdgaN5vKM+xP7yJtXVu+nfdVFmlgFDEKAe4Pyw==", "dev": true, "funding": [ { @@ -8058,7 +6271,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.12", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -8068,6 +6281,8 @@ }, "node_modules/prelude-ls": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", "dev": true, "license": "MIT", "engines": { @@ -8075,7 +6290,9 @@ } }, "node_modules/process-warning": { - "version": "5.0.0", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.1.0.tgz", + "integrity": "sha512-jQSaVHsPgtyw60e1rQ/A+/ArPEj/S8pS/vFnyGa/gYFXrKk/6RuDkoqVDQ5NI5MmS01698ltlAk0NoDBNLujRw==", "funding": [ { "type": "github", @@ -8090,6 +6307,8 @@ }, "node_modules/proxy-addr": { "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -8101,6 +6320,8 @@ }, "node_modules/pstree.remy": { "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", "dev": true, "license": "MIT" }, @@ -8117,6 +6338,8 @@ }, "node_modules/punycode": { "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true, "license": "MIT", "engines": { @@ -8141,6 +6364,8 @@ }, "node_modules/quick-format-unescaped": { "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", "license": "MIT" }, "node_modules/range-parser": { @@ -8158,6 +6383,8 @@ }, "node_modules/raw-body": { "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -8170,10 +6397,12 @@ } }, "node_modules/read-last-lines": { - "version": "1.8.0", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/read-last-lines/-/read-last-lines-1.9.0.tgz", + "integrity": "sha512-toU8ilGhDQrubHz4uOL6RNPd2chZUAFFQrjzMaik7PsbImJYVdpNw87oc3Ctav1eY1yM+W0ntGt88iYSmD13kg==", "license": "MIT", - "dependencies": { - "mz": "^2.7.0" + "engines": { + "node": ">=20" } }, "node_modules/readdirp": { @@ -8191,6 +6420,8 @@ }, "node_modules/real-require": { "version": "0.2.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz", + "integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==", "license": "MIT", "engines": { "node": ">= 12.13.0" @@ -8198,6 +6429,8 @@ }, "node_modules/redefine-property": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/redefine-property/-/redefine-property-3.0.1.tgz", + "integrity": "sha512-/YYmIi0V+vlg2B/QwwOr/SAirVKYdI9iF+KFRPMK5y2QqGq7Ky6PgxNBVXcuVq9E2o0clC4j2EyC1eigpbW8bA==", "license": "MIT", "dependencies": { "is-plain-obj": "^4.1.0" @@ -8208,105 +6441,14 @@ }, "node_modules/reflect-metadata": { "version": "0.1.14", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.14.tgz", + "integrity": "sha512-ZhYeb6nRaXCfhnndflDK8qI6ZQ/YcWZCISRAWICW9XYqMUwjZM9Z0DveWX/ABN01oxSHwVxKQmxeYZSsm0jh5A==", "license": "Apache-2.0" }, - "node_modules/reflect.getprototypeof": { - "version": "1.0.10", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.9", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.7", - "get-proto": "^1.0.1", - "which-builtin-type": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regenerate": { - "version": "1.4.2", - "dev": true, - "license": "MIT" - }, - "node_modules/regenerate-unicode-properties": { - "version": "10.2.2", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regenerator-runtime": { - "version": "0.14.1", - "dev": true, - "license": "MIT" - }, - "node_modules/regexp.prototype.flags": { - "version": "1.5.4", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "define-properties": "^1.2.1", - "es-errors": "^1.3.0", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "set-function-name": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/regexpu-core": { - "version": "6.4.0", - "dev": true, - "license": "MIT", - "dependencies": { - "regenerate": "^1.4.2", - "regenerate-unicode-properties": "^10.2.2", - "regjsgen": "^0.8.0", - "regjsparser": "^0.13.0", - "unicode-match-property-ecmascript": "^2.0.0", - "unicode-match-property-value-ecmascript": "^2.2.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/regjsgen": { - "version": "0.8.0", - "dev": true, - "license": "MIT" - }, - "node_modules/regjsparser": { - "version": "0.13.2", - "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.13.2.tgz", - "integrity": "sha512-NgRBy2Nx/bE+9F27nVHnqcN5HjyLmecqsqx2PJHu3/IEtADD4WuxuXIVExD5PoSDFVrl78dOonfcOe5O+5nbzQ==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "jsesc": "~3.1.0" - }, - "bin": { - "regjsparser": "bin/parser" - } - }, "node_modules/require-from-string": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -8314,6 +6456,8 @@ }, "node_modules/reserved-identifiers": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/reserved-identifiers/-/reserved-identifiers-1.2.0.tgz", + "integrity": "sha512-yE7KUfFvaBFzGPs5H3Ops1RevfUEsDc5Iz65rOwWg4lE8HJSYtle77uul3+573457oHvBKuHYDl/xqUkKpEEdw==", "dev": true, "license": "MIT", "engines": { @@ -8323,36 +6467,24 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/resolve": { - "version": "1.22.12", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", - "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", "dev": true, "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" } }, "node_modules/rolldown": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.3.tgz", - "integrity": "sha512-1F1eEtUBtFvcGm1HQ9TiUIUHPQG7mSAODrhIzjxoUEFuo8OcbrGLiVLkevNgj84TE4lnHvnumwFjhJO5Eu135g==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.3.tgz", + "integrity": "sha512-rn9wpmxplLf7NLNyCk9FyWh3FM43DbY8jOzCdEPzH7uflhTftRbCEpqi6Ly2osgoU8OwObtmavMbWLaWy4LX7A==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.137.0", + "@oxc-project/types": "=0.143.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -8362,25 +6494,36 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.1.3", - "@rolldown/binding-darwin-arm64": "1.1.3", - "@rolldown/binding-darwin-x64": "1.1.3", - "@rolldown/binding-freebsd-x64": "1.1.3", - "@rolldown/binding-linux-arm-gnueabihf": "1.1.3", - "@rolldown/binding-linux-arm64-gnu": "1.1.3", - "@rolldown/binding-linux-arm64-musl": "1.1.3", - "@rolldown/binding-linux-ppc64-gnu": "1.1.3", - "@rolldown/binding-linux-s390x-gnu": "1.1.3", - "@rolldown/binding-linux-x64-gnu": "1.1.3", - "@rolldown/binding-linux-x64-musl": "1.1.3", - "@rolldown/binding-openharmony-arm64": "1.1.3", - "@rolldown/binding-wasm32-wasi": "1.1.3", - "@rolldown/binding-win32-arm64-msvc": "1.1.3", - "@rolldown/binding-win32-x64-msvc": "1.1.3" + "@rolldown/binding-android-arm64": "1.2.3", + "@rolldown/binding-darwin-arm64": "1.2.3", + "@rolldown/binding-darwin-x64": "1.2.3", + "@rolldown/binding-freebsd-x64": "1.2.3", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.3", + "@rolldown/binding-linux-arm64-gnu": "1.2.3", + "@rolldown/binding-linux-arm64-musl": "1.2.3", + "@rolldown/binding-linux-ppc64-gnu": "1.2.3", + "@rolldown/binding-linux-s390x-gnu": "1.2.3", + "@rolldown/binding-linux-x64-gnu": "1.2.3", + "@rolldown/binding-linux-x64-musl": "1.2.3", + "@rolldown/binding-openharmony-arm64": "1.2.3", + "@rolldown/binding-win32-arm64-msvc": "1.2.3", + "@rolldown/binding-win32-x64-msvc": "1.2.3" + } + }, + "node_modules/rolldown/node_modules/@oxc-project/types": { + "version": "0.143.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.143.0.tgz", + "integrity": "sha512-u6JZdLBTLotrNC9Vd6vPssINdzcCzleKAH6EJKImQb7GtYvX5keN2dxkoK44stCc4tffE6QQRtZTXVSzsLUlWA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, "node_modules/router": { "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -8393,59 +6536,10 @@ "node": ">= 18" } }, - "node_modules/safe-array-concat": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.4.tgz", - "integrity": "sha512-wtZlHyOje6OZTGqAoaDKxFkgRtkF9CnHAVnCHKfuj200wAgL+bSJhdsCD2l0Qx/2ekEXjPWcyKkfGb5CPboslg==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.9", - "call-bound": "^1.0.4", - "get-intrinsic": "^1.3.0", - "has-symbols": "^1.1.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-push-apply": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "isarray": "^2.0.5" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/safe-stable-stringify": { "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", "license": "MIT", "engines": { "node": ">=10" @@ -8453,10 +6547,14 @@ }, "node_modules/safer-buffer": { "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "license": "MIT" }, "node_modules/say": { "version": "0.16.0", + "resolved": "https://registry.npmjs.org/say/-/say-0.16.0.tgz", + "integrity": "sha512-yEfncNu3I6lcZ6RIrXgE9DqbrEmvV5uQQ8ReM14u/DodlvJYpveqNphO55RLMSj77b06ZKNif/FLmhzQxcuUXg==", "license": "MIT", "dependencies": { "one-time": "0.0.4" @@ -8482,16 +6580,10 @@ ], "license": "BSD-3-Clause" }, - "node_modules/semver": { - "version": "6.3.1", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/send": { "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { "debug": "^4.4.3", @@ -8516,6 +6608,8 @@ }, "node_modules/serialport": { "version": "13.0.0", + "resolved": "https://registry.npmjs.org/serialport/-/serialport-13.0.0.tgz", + "integrity": "sha512-PHpnTd8isMGPfFTZNCzOZp9m4mAJSNWle9Jxu6BPTcWq7YXl5qN7tp8Sgn0h+WIGcD6JFz5QDgixC2s4VW7vzg==", "license": "MIT", "dependencies": { "@serialport/binding-mock": "10.2.2", @@ -8542,6 +6636,8 @@ }, "node_modules/serialport/node_modules/debug": { "version": "4.4.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", + "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -8557,6 +6653,8 @@ }, "node_modules/serve-static": { "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", "dependencies": { "encodeurl": "^2.0.0", @@ -8574,6 +6672,8 @@ }, "node_modules/set-error-class": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/set-error-class/-/set-error-class-3.0.1.tgz", + "integrity": "sha512-VEsI0yEUMEnXmSkhfj+EwzNC1om7zGy2IdymNyi4FfGzmoxvXXIB1Nv09+DfRgsd7fS+u+FwTqCTSRhhNMcx8w==", "license": "MIT", "dependencies": { "normalize-exception": "^4.0.1" @@ -8584,6 +6684,8 @@ }, "node_modules/set-error-message": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/set-error-message/-/set-error-message-3.0.1.tgz", + "integrity": "sha512-5+TX4opc5Hus1nu5UDQq0y5SuOCvDqQFh+yGcKgzJDKvohVcU51iE2XtTxw/V7VsZ7xA142tei+KSq3ZIB/dNw==", "license": "MIT", "dependencies": { "normalize-exception": "^4.0.1" @@ -8594,6 +6696,8 @@ }, "node_modules/set-error-props": { "version": "6.0.1", + "resolved": "https://registry.npmjs.org/set-error-props/-/set-error-props-6.0.1.tgz", + "integrity": "sha512-P27Piv/xn8Iy+F+xLdY3Qk3Ne63bHFeXgFYc6HAxsKqbzZD3RXrqlLZ+97ObdCn1+o8Pc0qlw6qFbHVnOmKjtA==", "license": "MIT", "dependencies": { "is-error-instance": "^3.0.1", @@ -8606,6 +6710,8 @@ }, "node_modules/set-error-stack": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/set-error-stack/-/set-error-stack-3.0.1.tgz", + "integrity": "sha512-fMe1RawtS1IuX0xtdRWbMOOqvyMLLv46KKEHUcI6MAPj6EmEMMihrGpm5eu4RpCrIL05fjvtH+KxNV7NwStpLw==", "license": "MIT", "dependencies": { "normalize-exception": "^4.0.1" @@ -8631,48 +6737,12 @@ "node": ">= 0.4" } }, - "node_modules/set-function-name": { - "version": "2.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "functions-have-names": "^1.2.3", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/set-proto": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "dunder-proto": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/setprototypeof": { "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, - "node_modules/shallow-clone": { - "version": "3.0.1", - "dev": true, - "license": "MIT", - "dependencies": { - "kind-of": "^6.0.2" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -8731,6 +6801,8 @@ }, "node_modules/side-channel-map": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -8747,6 +6819,8 @@ }, "node_modules/side-channel-weakmap": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", "dependencies": { "call-bound": "^1.0.2", @@ -8764,11 +6838,15 @@ }, "node_modules/siginfo": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", "dev": true, "license": "ISC" }, "node_modules/simple-update-notifier": { "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", "dev": true, "license": "MIT", "dependencies": { @@ -8800,8 +6878,23 @@ "node": ">=6" } }, + "node_modules/smol-toml": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.7.1.tgz", + "integrity": "sha512-PPlsspAZ4jbMBu5DMFhfUGDQLu/vrL4SyBROVS37x8ynnVmFIs1VPBz1Co8Xks3TvpIaZXmU85y4DrQ+UyVFoQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">= 18" + }, + "funding": { + "url": "https://github.com/sponsors/cyyynthia" + } + }, "node_modules/socket.io": { "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", + "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", "license": "MIT", "dependencies": { "accepts": "~1.3.4", @@ -8843,9 +6936,9 @@ } }, "node_modules/socket.io-parser": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", - "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", + "version": "4.2.7", + "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.7.tgz", + "integrity": "sha512-IH/iSeO9T6gz1KkFleGDWkG9N3dl4jXVYUtMhIqH10Md0ttMer8nUNWiP1DKuNrybD2xBrixLJdCC9J6ECoYkg==", "license": "MIT", "dependencies": { "@socket.io/component-emitter": "~3.1.0", @@ -8857,6 +6950,8 @@ }, "node_modules/socket.io/node_modules/accepts": { "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "license": "MIT", "dependencies": { "mime-types": "~2.1.34", @@ -8868,6 +6963,8 @@ }, "node_modules/socket.io/node_modules/mime-db": { "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -8875,6 +6972,8 @@ }, "node_modules/socket.io/node_modules/mime-types": { "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "license": "MIT", "dependencies": { "mime-db": "1.52.0" @@ -8885,6 +6984,8 @@ }, "node_modules/socket.io/node_modules/negotiator": { "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -8899,14 +7000,6 @@ "atomic-sleep": "^1.0.0" } }, - "node_modules/source-map": { - "version": "0.6.1", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -8917,22 +7010,17 @@ "node": ">=0.10.0" } }, - "node_modules/source-map-support": { - "version": "0.5.21", - "dev": true, - "license": "MIT", - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, "node_modules/spdx-exceptions": { "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", "dev": true, "license": "CC-BY-3.0" }, "node_modules/spdx-expression-parse": { "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", "dev": true, "license": "MIT", "dependencies": { @@ -8964,6 +7052,8 @@ }, "node_modules/split2": { "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", "license": "ISC", "engines": { "node": ">= 10.x" @@ -8971,93 +7061,27 @@ }, "node_modules/stackback": { "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", "dev": true, "license": "MIT" }, "node_modules/statuses": { "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/std-env": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.1.0.tgz", - "integrity": "sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", + "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", "dev": true, "license": "MIT" }, - "node_modules/stop-iteration-iterator": { - "version": "1.1.0", - "dev": true, - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "internal-slot": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/string.prototype.trim": { - "version": "1.2.11", - "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.11.tgz", - "integrity": "sha512-PwvK7BU+CMTJGYQCTZb5RWXIML92lftJLhQz1tBzgKiqGxJaMlBAa48POXaNAC2s4y8jr3EFqrkF9+44neS46w==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.9", - "call-bound": "^1.0.4", - "define-data-property": "^1.1.4", - "define-properties": "^1.2.1", - "es-abstract": "^1.24.2", - "es-object-atoms": "^1.1.2", - "has-property-descriptors": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimend": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.10.tgz", - "integrity": "sha512-2+3aDAOmPTmuFwjDnmJG2ctEkQKVki7vOSqaxkv42Mowj1V6PnvuwFCRrR5lChUux1TBskPjfkeTOhqczDMxTw==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.9", - "call-bound": "^1.0.4", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/string.prototype.trimstart": { - "version": "1.0.8", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-object-atoms": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/strip-json-comments": { "version": "5.0.3", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", @@ -9087,19 +7111,10 @@ }, "bin": { "sucrase": "bin/sucrase", - "sucrase-node": "bin/sucrase-node" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - } - }, - "node_modules/sucrase/node_modules/commander": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", - "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", - "license": "MIT", + "sucrase-node": "bin/sucrase-node" + }, "engines": { - "node": ">= 6" + "node": ">=16 || 14 >=14.17" } }, "node_modules/superagent": { @@ -9150,19 +7165,10 @@ "node": ">=8" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/thenify": { "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", "license": "MIT", "dependencies": { "any-promise": "^1.0.0" @@ -9170,6 +7176,8 @@ }, "node_modules/thenify-all": { "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", "license": "MIT", "dependencies": { "thenify": ">= 3.1.0 < 4" @@ -9198,13 +7206,15 @@ }, "node_modules/tinybench": { "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", "dev": true, "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", - "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", + "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", "dev": true, "license": "MIT", "engines": { @@ -9227,39 +7237,10 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", - "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", - "license": "MIT", - "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "picomatch": "^3 || ^4" - }, - "peerDependenciesMeta": { - "picomatch": { - "optional": true - } - } - }, - "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/tinyrainbow": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.0.tgz", - "integrity": "sha512-Bf+ILmBgretUrdJxzXM0SgXLZ3XfiaUuOj/IKQHuTXip+05Xn+uyEYdVg0kYDipTBcLrCVyUzAPz7QmArb0mmw==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-3.1.1.tgz", + "integrity": "sha512-yau8yJdTt989Mm0Bd/236QnzEiPf2xLLTqUZRUJOo/3CB078LSwzei343DgtJVmfJKJE3TMINY1u42SQsP6mXw==", "dev": true, "license": "MIT", "engines": { @@ -9289,6 +7270,8 @@ }, "node_modules/to-valid-identifier": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/to-valid-identifier/-/to-valid-identifier-1.0.0.tgz", + "integrity": "sha512-41wJyvKep3yT2tyPqX/4blcfybknGB4D+oETKLs7Q76UiPqRpUJK3hr1nxelyYO0PHKVzJwlu0aCeEAsGI6rpw==", "dev": true, "license": "MIT", "dependencies": { @@ -9304,6 +7287,8 @@ }, "node_modules/toidentifier": { "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "license": "MIT", "engines": { "node": ">=0.6" @@ -9311,6 +7296,8 @@ }, "node_modules/touch": { "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", "dev": true, "license": "ISC", "bin": { @@ -9361,6 +7348,8 @@ }, "node_modules/type-check": { "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", "dev": true, "license": "MIT", "dependencies": { @@ -9370,13 +7359,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-detect": { - "version": "4.1.0", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/type-is": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", @@ -9408,78 +7390,6 @@ "url": "https://opencollective.com/express" } }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/typed-array-byte-length": { - "version": "1.0.3", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-byte-offset": { - "version": "1.0.4", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "for-each": "^0.3.3", - "gopd": "^1.2.0", - "has-proto": "^1.2.0", - "is-typed-array": "^1.1.15", - "reflect.getprototypeof": "^1.0.9" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/typed-array-length": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.8.tgz", - "integrity": "sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.9", - "for-each": "^0.3.5", - "gopd": "^1.2.0", - "is-typed-array": "^1.1.15", - "possible-typed-array-names": "^1.1.0", - "reflect.getprototypeof": "^1.0.10" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/typescript": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.3.tgz", @@ -9495,16 +7405,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.62.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.62.0.tgz", - "integrity": "sha512-8QxXi+ZACKX0kaqO4gY8kn0RSD9gFfaHDWwjqtEN48aWCBkX4MJaufWN+c3BzlrXLOxfywDL8CaoqUwcRq4j4Q==", + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.66.0.tgz", + "integrity": "sha512-QlEbBPz/RuJ1XUHj29nm3t0F/O/cSlEnntozqPOYHnnTGAXFamnMBu5i9Vn6vhUPHGAjR+Vl+5J8vPN/BMUrJw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.62.0", - "@typescript-eslint/parser": "8.62.0", - "@typescript-eslint/typescript-estree": "8.62.0", - "@typescript-eslint/utils": "8.62.0" + "@typescript-eslint/eslint-plugin": "8.66.0", + "@typescript-eslint/parser": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0", + "@typescript-eslint/utils": "8.66.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -9518,25 +7428,20 @@ "typescript": ">=4.8.4 <6.1.0" } }, - "node_modules/unbox-primitive": { - "version": "1.1.0", + "node_modules/unbash": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/unbash/-/unbash-4.0.6.tgz", + "integrity": "sha512-YGBMSVG/WrA2vgaZbXVvxrGgoMcWZecyyS4foGt8clbtY7s1nIKNmt7Z9LR74XE6FkYTSqDmKk2lIOhOjIvmrw==", "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "has-bigints": "^1.0.2", - "has-symbols": "^1.1.0", - "which-boxed-primitive": "^1.1.1" - }, + "license": "ISC", "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=14" } }, "node_modules/undefsafe": { "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", "dev": true, "license": "MIT" }, @@ -9546,44 +7451,10 @@ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", "license": "MIT" }, - "node_modules/unicode-canonical-property-names-ecmascript": { - "version": "2.0.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-ecmascript": { - "version": "2.0.0", - "dev": true, - "license": "MIT", - "dependencies": { - "unicode-canonical-property-names-ecmascript": "^2.0.0", - "unicode-property-aliases-ecmascript": "^2.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-match-property-value-ecmascript": { - "version": "2.2.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/unicode-property-aliases-ecmascript": { - "version": "2.2.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/unique-names-generator": { "version": "4.7.1", + "resolved": "https://registry.npmjs.org/unique-names-generator/-/unique-names-generator-4.7.1.tgz", + "integrity": "sha512-lMx9dX+KRmG8sq6gulYYpKWZc9RlGsgBR6aoO8Qsm3qvkSJ+3rAymr+TnV8EDMrIrwuFJ4kruzMWM/OpYzPoow==", "license": "MIT", "engines": { "node": ">=8" @@ -9600,42 +7471,17 @@ }, "node_modules/unpipe": { "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "license": "MIT", "engines": { "node": ">= 0.8" } }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, "node_modules/uri-js": { "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -9643,9 +7489,9 @@ } }, "node_modules/usb": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/usb/-/usb-3.0.0.tgz", - "integrity": "sha512-oKHicfd16YtVrh+icdjjTE+CNpAohAjbt8ecanLhA0F9mVjXnGsu1UoV9vZg9sWQ51Lyc5XVI/jFdqXw5IjNFQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/usb/-/usb-3.0.1.tgz", + "integrity": "sha512-IQIi5EraqmKssMNcXe7afKhriHFAE5WdfykhvgkOschJyHBkJXtk/PDx/DzVNyfI7qdr+BUbmLziXA+I3z02lw==", "license": "MIT", "dependencies": { "@types/w3c-web-usb": "^1.0.14" @@ -9654,16 +7500,16 @@ "node": ">= 12.22.0 < 13 || >= 14.17.0 < 15 || >= 15.12.0 < 16 || >= 16.0.0" }, "optionalDependencies": { - "@node-usb/usb-darwin-arm64": "3.0.0", - "@node-usb/usb-darwin-x64": "3.0.0", - "@node-usb/usb-linux-arm-gnueabihf": "3.0.0", - "@node-usb/usb-linux-arm64-gnu": "3.0.0", - "@node-usb/usb-linux-arm64-musl": "3.0.0", - "@node-usb/usb-linux-x64-gnu": "3.0.0", - "@node-usb/usb-linux-x64-musl": "3.0.0", - "@node-usb/usb-win32-arm64-msvc": "3.0.0", - "@node-usb/usb-win32-ia32-msvc": "3.0.0", - "@node-usb/usb-win32-x64-msvc": "3.0.0" + "@node-usb/usb-darwin-arm64": "3.0.1", + "@node-usb/usb-darwin-x64": "3.0.1", + "@node-usb/usb-linux-arm-gnueabihf": "3.0.1", + "@node-usb/usb-linux-arm64-gnu": "3.0.1", + "@node-usb/usb-linux-arm64-musl": "3.0.1", + "@node-usb/usb-linux-x64-gnu": "3.0.1", + "@node-usb/usb-linux-x64-musl": "3.0.1", + "@node-usb/usb-win32-arm64-msvc": "3.0.1", + "@node-usb/usb-win32-ia32-msvc": "3.0.1", + "@node-usb/usb-win32-x64-msvc": "3.0.1" } }, "node_modules/uuid": { @@ -9679,35 +7525,26 @@ "uuid": "dist-node/bin/uuid" } }, - "node_modules/v8flags": { - "version": "3.2.0", - "dev": true, - "license": "MIT", - "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, "node_modules/vary": { "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "license": "MIT", "engines": { "node": ">= 0.8" } }, "node_modules/vite": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.0.tgz", - "integrity": "sha512-BuJcQK/56NQTWDGn4ABea3q4SSBdNPWwNZKTkkUpcMPnLoquSYH8llRtSUIgoL1KSCpHt5eghLShn50mH36y7Q==", + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.0.tgz", + "integrity": "sha512-pn+CFpM0lwDeKwmOq1ZaBK/9sjorZcgqxki6MbY/jPEVd9vichIlmlD4HmQ5wdP5EgqQCFRaACBxMC7uEGc6lQ==", "dev": true, "license": "MIT", "dependencies": { - "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.15", - "rolldown": "~1.1.2", + "lightningcss": "^1.33.0", + "picomatch": "^4.0.5", + "postcss": "^8.5.23", + "rolldown": "~1.2.0", "tinyglobby": "^0.2.17" }, "bin": { @@ -9724,7 +7561,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.3.0", + "@vitejs/devtools": "^0.4.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -9775,33 +7612,20 @@ } } }, - "node_modules/vite/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, "node_modules/vitest": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.9.tgz", - "integrity": "sha512-nE3/LEyc0z87uHYLZebqCUOaJr2hdtuPp7BQ4BosVFnfltxgAvMG08NyrSGlPpOUWvR27c5flSmYFTNr78L9GQ==", + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-4.1.10.tgz", + "integrity": "sha512-R9jUTe5S4Qb0HCd4TNqpC7oGcrMssMRGXLW80ubjWsW9VH5GF8y1Y0SFLY9AbqSk6nt0PnOx4H4WNJYZ13GUPw==", "dev": true, "license": "MIT", "dependencies": { - "@vitest/expect": "4.1.9", - "@vitest/mocker": "4.1.9", - "@vitest/pretty-format": "4.1.9", - "@vitest/runner": "4.1.9", - "@vitest/snapshot": "4.1.9", - "@vitest/spy": "4.1.9", - "@vitest/utils": "4.1.9", + "@vitest/expect": "4.1.10", + "@vitest/mocker": "4.1.10", + "@vitest/pretty-format": "4.1.10", + "@vitest/runner": "4.1.10", + "@vitest/snapshot": "4.1.10", + "@vitest/spy": "4.1.10", + "@vitest/utils": "4.1.10", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", @@ -9829,12 +7653,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "4.1.9", - "@vitest/browser-preview": "4.1.9", - "@vitest/browser-webdriverio": "4.1.9", - "@vitest/coverage-istanbul": "4.1.9", - "@vitest/coverage-v8": "4.1.9", - "@vitest/ui": "4.1.9", + "@vitest/browser-playwright": "4.1.10", + "@vitest/browser-preview": "4.1.10", + "@vitest/browser-webdriverio": "4.1.10", + "@vitest/coverage-istanbul": "4.1.10", + "@vitest/coverage-v8": "4.1.10", + "@vitest/ui": "4.1.10", "happy-dom": "*", "jsdom": "*", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" @@ -9892,17 +7716,14 @@ "vitest": ">=3.0.0" } }, - "node_modules/vitest/node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "node_modules/walk-up-path": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/walk-up-path/-/walk-up-path-4.0.0.tgz", + "integrity": "sha512-3hu+tD8YzSLGuFYtPRb48vdhKMi0KQV5sn+uWr8+7dMEq/2G/dtLrdDinkLjqq5TIbIBjYJ4Ax/n3YiaW7QM8A==", "dev": true, - "license": "MIT", + "license": "ISC", "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "node": "20 || >=22" } }, "node_modules/which": { @@ -9920,91 +7741,10 @@ "node": ">= 8" } }, - "node_modules/which-boxed-primitive": { - "version": "1.1.1", - "dev": true, - "license": "MIT", - "dependencies": { - "is-bigint": "^1.1.0", - "is-boolean-object": "^1.2.1", - "is-number-object": "^1.1.1", - "is-string": "^1.1.1", - "is-symbol": "^1.1.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-builtin-type": { - "version": "1.2.1", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "function.prototype.name": "^1.1.6", - "has-tostringtag": "^1.0.2", - "is-async-function": "^2.0.0", - "is-date-object": "^1.1.0", - "is-finalizationregistry": "^1.1.0", - "is-generator-function": "^1.0.10", - "is-regex": "^1.2.1", - "is-weakref": "^1.0.2", - "isarray": "^2.0.5", - "which-boxed-primitive": "^1.1.0", - "which-collection": "^1.0.2", - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-collection": { - "version": "1.0.2", - "dev": true, - "license": "MIT", - "dependencies": { - "is-map": "^2.0.3", - "is-set": "^2.0.3", - "is-weakmap": "^2.0.2", - "is-weakset": "^2.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/which-typed-array": { - "version": "1.1.22", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.22.tgz", - "integrity": "sha512-fvO4ExWMFsqyhG3AiPAObMuY1lxaqgYcxbc49CNdWDDECOJNgQyvsOWVwbZc+qf3rzRtxojBK+CMEv0Ld5CYpw==", - "dev": true, - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.9", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/why-is-node-running": { "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", "dev": true, "license": "MIT", "dependencies": { @@ -10020,6 +7760,8 @@ }, "node_modules/word-wrap": { "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, "license": "MIT", "engines": { @@ -10028,6 +7770,8 @@ }, "node_modules/wrap-error-message": { "version": "3.0.1", + "resolved": "https://registry.npmjs.org/wrap-error-message/-/wrap-error-message-3.0.1.tgz", + "integrity": "sha512-TjLYwoDEMdPs0h6LfPukMZn2R7tmywbWyRETDosqsE1Gs0mayuSiDQm1MzeDPc8VMTvS4yhLPgICkSXvTeo9ng==", "license": "MIT", "dependencies": { "normalize-exception": "^4.0.1", @@ -10039,12 +7783,14 @@ }, "node_modules/wrappy": { "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "license": "ISC" }, "node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", + "version": "8.21.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.2.tgz", + "integrity": "sha512-54dMVAo4WIe6SKy3vBgN+9bJZqqQ8IMRevAkOLQALhi49qkkQDQfWdAZ8KQlXiEabw88ARXXdUrlvtbKQX+aKw==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -10071,11 +7817,6 @@ "node": ">=0.4.0" } }, - "node_modules/yallist": { - "version": "3.1.1", - "dev": true, - "license": "ISC" - }, "node_modules/yaml": { "version": "2.9.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", @@ -10093,6 +7834,8 @@ }, "node_modules/yocto-queue": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", "dev": true, "license": "MIT", "engines": { @@ -10101,6 +7844,16 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", + "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } } } } diff --git a/package.json b/package.json index a7f6ec05..0d131d1b 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,10 @@ "license": "LGPL-3.0-only", "type": "module", "dependencies": { + "@serialport/bindings-interface": "^1.2.2", + "@serialport/stream": "^13.0.0", "@sinclair/typebox": "^0.34.47", - "@stoprocent/noble": "^2.3.17", + "@stoprocent/noble": "^2.7.1", "@timesplinter/pimple": "^2.1.1", "@timesplinter/sequential-task-queue": "^1.4.0", "ajv": "^8.17.1", @@ -28,33 +30,28 @@ "socket.io": "^4.8.3", "speaker": "https://github.com/SlvCtrlPlus/node-speaker/releases/download/v0.1.0/speaker-v0.1.0.tgz", "sucrase": "^3.35.1", - "type-detect": "^4.1.0", "unique-names-generator": "^4.7.1", "usb": "^3.0.0", "uuid": "^14.0.0" }, "devDependencies": { - "@babel/core": "^7.22.8", - "@babel/node": "^7.22.6", - "@babel/plugin-proposal-decorators": "^7.22.7", - "@babel/preset-env": "^7.14.4", - "@babel/preset-typescript": "^7.22.5", "@eslint/js": "^10.0.1", - "@serialport/binding-mock": "^10.2.2", "@stylistic/eslint-plugin": "^5.10.0", "@types/cors": "^2.8.10", "@types/express": "^5.0.6", "@types/node": "^24.1.0", "@types/supertest": "^7.2.0", "@types/type-detect": "^4.0.3", + "@types/w3c-web-usb": "^1.0.14", "@types/ws": "^8.18.1", "@vitest/coverage-v8": "^4.0.16", + "@vitest/expect": "^4.1.10", "eslint": "^10.1.0", - "eslint-config-prettier": "^10.1.8", "eslint-plugin-jsdoc": "^62.8.0", "eslint-plugin-prefer-arrow-functions": "^3.10.1", "globals": "^17.0.0", "jiti": "^2.6.1", + "knip": "^6.31.0", "nodemon": "^3.0.2", "pino-pretty": "^13.1.3", "socket.io-client": "^4.8.3", @@ -77,6 +74,8 @@ "test:coverage": "vitest run --project unit --coverage", "typecheck": "tsc --noEmit && tsc --noEmit -p tests/tsconfig.json", "lint": "eslint -c eslint.config.ts .", + "knip": "knip", + "check": "npm run typecheck && npm run lint && npm run knip", "build": "tsc" } } diff --git a/src/app.ts b/src/app.ts index bccbd100..c02da3a6 100644 --- a/src/app.ts +++ b/src/app.ts @@ -45,7 +45,7 @@ export type AppOptions = { dataPath: string; }; -export type ServeResult = { +type ServeResult = { httpServer: http.Server; httpsServer?: https.Server; }; diff --git a/src/automation/scriptRuntime.ts b/src/automation/scriptRuntime.ts index 0306778f..9bab72ca 100644 --- a/src/automation/scriptRuntime.ts +++ b/src/automation/scriptRuntime.ts @@ -1,6 +1,6 @@ -import { AnyDevice, DeviceNotification } from '../device/device.js'; +import { AnyDevice, AnyDeviceNotification } from '../device/device.js'; import fs, { WriteStream } from 'fs'; -import readLastLines from 'read-last-lines/dist/index.js'; +import readLastLines from 'read-last-lines'; import EventEmitter from 'events'; import AutomationEventType from './automationEventType.js'; import { DeviceManagerEvent } from '../device/deviceManager.js'; @@ -10,7 +10,7 @@ import ScriptVm from './scriptVm.js'; export type SupportedDeviceEvent = | { type: DeviceManagerEvent.deviceConnected | DeviceManagerEvent.deviceDisconnected | DeviceManagerEvent.deviceRefreshed, device: AnyDevice, args: [] } - | { type: DeviceManagerEvent.deviceNotification, device: AnyDevice, args: [notification: DeviceNotification] }; + | { type: DeviceManagerEvent.deviceNotification, device: AnyDevice, args: [notification: AnyDeviceNotification] }; type ScriptRuntimeEvents = { [AutomationEventType.consoleLog]: (data: string) => void; diff --git a/src/automation/scriptVm.ts b/src/automation/scriptVm.ts index b84e79f8..fa9b1135 100644 --- a/src/automation/scriptVm.ts +++ b/src/automation/scriptVm.ts @@ -7,7 +7,8 @@ export type ScriptVmSignalEvents = { }; export const LIFECYCLE_START = 'start'; -export const LIFECYCLE_STOP = 'stop'; + +const LIFECYCLE_STOP = 'stop'; type LifecyclePhase = typeof LIFECYCLE_START | typeof LIFECYCLE_STOP; export default class ScriptVm diff --git a/src/automation/scriptVmFactory.ts b/src/automation/scriptVmFactory.ts index 4bb0635b..2a23c34b 100644 --- a/src/automation/scriptVmFactory.ts +++ b/src/automation/scriptVmFactory.ts @@ -7,12 +7,12 @@ import { AnyDevice } from '../device/device.js'; import Logger from '../logging/Logger.js'; import ScriptVm, { LIFECYCLE_START, ScriptVmSignalEvents } from './scriptVm.js'; -export type BridgeDevice = { +type BridgeDevice = { id: string; name: string; }; -export const toBridgeDevice = (device: AnyDevice): BridgeDevice => { +const toBridgeDevice = (device: AnyDevice): BridgeDevice => { return { id: device.getDeviceId, name: device.getDeviceName }; }; diff --git a/src/controller/automation/createScriptController.ts b/src/controller/automation/createScriptController.ts index fc19d365..45a2f7e1 100644 --- a/src/controller/automation/createScriptController.ts +++ b/src/controller/automation/createScriptController.ts @@ -7,7 +7,7 @@ type RequestParams = { fileName: string; }; type RequestBody = string; -type CreateScriptRequest = Request; +type CreateScriptRequest = Request; export default class CreateScriptController implements ControllerInterface { diff --git a/src/controller/automation/runScriptController.ts b/src/controller/automation/runScriptController.ts index 18dba7a2..fa9bc8bd 100644 --- a/src/controller/automation/runScriptController.ts +++ b/src/controller/automation/runScriptController.ts @@ -2,7 +2,7 @@ import { Request, Response } from 'express'; import ControllerInterface from '../controllerInterface.js'; import ScriptRuntime from '../../automation/scriptRuntime.js'; -type RunScriptRequest = Request; +type RunScriptRequest = Request; export default class RunScriptController implements ControllerInterface { diff --git a/src/controller/patchDeviceController.ts b/src/controller/patchDeviceController.ts index 558593fe..73ef6970 100644 --- a/src/controller/patchDeviceController.ts +++ b/src/controller/patchDeviceController.ts @@ -6,7 +6,7 @@ import DeviceUpdaterInterface from '../device/updater/deviceUpdaterInterface.js' import { DeviceData } from '../device/device.js'; import { DeviceId } from '../device/deviceId.js'; -type PatchDeviceRequest = Request<{ deviceId: DeviceId }, any, DeviceData>; +type PatchDeviceRequest = Request<{ deviceId: DeviceId }, unknown, DeviceData>; export default class PatchDeviceController implements ControllerInterface { diff --git a/src/device/attribute/deviceAttribute.ts b/src/device/attribute/deviceAttribute.ts index 35863987..3915e568 100644 --- a/src/device/attribute/deviceAttribute.ts +++ b/src/device/attribute/deviceAttribute.ts @@ -70,7 +70,7 @@ export default abstract class DeviceAttribute; - public static isInstance(this: new (...args: any[]) => U, attr: unknown): attr is U { + public static isInstance(this: abstract new (...args: never[]) => U, attr: unknown): attr is U { return attr instanceof this; } } diff --git a/src/device/attribute/listDeviceAttribute.ts b/src/device/attribute/listDeviceAttribute.ts index 82c846c5..b9962fe1 100644 --- a/src/device/attribute/listDeviceAttribute.ts +++ b/src/device/attribute/listDeviceAttribute.ts @@ -2,13 +2,14 @@ import { Expose } from 'class-transformer'; import DeviceAttribute, { DeviceAttributeModifier, NotUndefined } from './deviceAttribute.js'; import { Int } from '../../util/numbers.js'; +type ListDeviceAttributeOption = { key: IKey, value: IValue }; + export type ListDeviceAttributeItem = string | Int; export type InitializedListDeviceAttribute< IKey extends ListDeviceAttributeItem, IValue extends ListDeviceAttributeItem, > = ListDeviceAttribute; -export type ListDeviceAttributeOption = { key: IKey, value: IValue }; export type ListDeviceAttributeOptions = ListDeviceAttributeOption[]; export default class ListDeviceAttribute< diff --git a/src/device/device.ts b/src/device/device.ts index 2401a38c..7cef660e 100644 --- a/src/device/device.ts +++ b/src/device/device.ts @@ -13,7 +13,7 @@ export type DeviceAttributes = Record; export type DeviceNotifications = JsonObject; export type NoDeviceNotifications = Record; -export type AnyDeviceNotifications = JsonObject; +type AnyDeviceNotifications = JsonObject; export type AttributeKeyOf = keyof A & string; export type AttributeValueOf> = @@ -38,19 +38,21 @@ export enum DeviceEvent { deviceNotification = 'deviceNotification', } -export type DeviceNotification = +type DeviceNotification = { [K in keyof TNotifications & string]: { type: K, data: TNotifications[K] } }[keyof TNotifications & string]; +export type AnyDeviceNotification = DeviceNotification; + export type DeviceEventMap< - TDevice extends Device = Device, - TNotifications extends DeviceNotifications = AnyDeviceNotifications, + TDevice extends AnyDevice, + TNotifications extends DeviceNotifications, > = { [DeviceEvent.deviceRefreshed]: [device: TDevice]; [DeviceEvent.deviceDisconnected]: [device: TDevice]; [DeviceEvent.deviceNotification]: [device: TDevice, notification: DeviceNotification]; }; -export type WithUntypedAttributes> = Omit & { +export type WithUntypedAttributes = Omit & { setAttribute(attributeName: string, value: AttributeValue): Promise; }; diff --git a/src/device/deviceConfig.ts b/src/device/deviceConfig.ts index 94356ca9..688c08f7 100644 --- a/src/device/deviceConfig.ts +++ b/src/device/deviceConfig.ts @@ -1,7 +1,8 @@ import { Type, Static, TObject } from '@sinclair/typebox'; export const noDeviceConfigSchema = Type.Object({}, { additionalProperties: false }); -export type NoDeviceConfigSchema = typeof noDeviceConfigSchema; -export type NoDeviceConfig = Static; +type NoDeviceConfigSchema = typeof noDeviceConfigSchema; + +export type NoDeviceConfig = Static; export type AnyDeviceConfig = Static; diff --git a/src/device/deviceManager.ts b/src/device/deviceManager.ts index cfaed464..7d95fb18 100644 --- a/src/device/deviceManager.ts +++ b/src/device/deviceManager.ts @@ -1,4 +1,4 @@ -import { AnyDevice, DeviceEvent, DeviceNotification } from './device.js'; +import { AnyDevice, AnyDeviceNotification, DeviceEvent } from './device.js'; import EventEmitter from 'events'; import { SequentialTaskQueue } from '@timesplinter/sequential-task-queue'; import DeviceState from './deviceState.js'; @@ -34,7 +34,7 @@ type DeviceManagerEventMap = { [DeviceManagerEvent.deviceDisconnected]: [device: AnyDevice]; [DeviceManagerEvent.deviceRefreshed]: [device: AnyDevice]; [DeviceManagerEvent.deviceDetected]: [deviceDetectionInfo: DeviceDetectionInfo]; - [DeviceManagerEvent.deviceNotification]: [device: AnyDevice, notification: DeviceNotification]; + [DeviceManagerEvent.deviceNotification]: [device: AnyDevice, notification: AnyDeviceNotification]; }; type ConnectedDevice = { device: AnyDevice, deviceDetectionInfo: DeviceDetectionInfo }; diff --git a/src/device/peripheralDevice.ts b/src/device/peripheralDevice.ts index 5f8b151d..acd83545 100644 --- a/src/device/peripheralDevice.ts +++ b/src/device/peripheralDevice.ts @@ -1,16 +1,16 @@ import Device, { DeviceAttributes, DeviceNotifications, NoDeviceNotifications, WithUntypedAttributes } from './device.js'; import BidirectionalDeviceTransport from './transport/deviceBidirectionalTransport.js'; -import DeviceProtocol, { MessageWithResponse } from './protocol/deviceProtocol.js'; +import DeviceProtocol, { AnyDeviceProtocol, AnyMessageWithResponse } from './protocol/deviceProtocol.js'; import { AnyDeviceConfig, NoDeviceConfig } from './deviceConfig.js'; import EventEmitter from 'events'; import { DeviceId } from './deviceId.js'; import Logger from '../logging/Logger.js'; import { logError } from '../util/error.js'; -export type AnyPeripheralDevice = WithUntypedAttributes>>>; +export type AnyPeripheralDevice = WithUntypedAttributes>; export default abstract class PeripheralDevice< - TProtocol extends DeviceProtocol>, + TProtocol extends DeviceProtocol, TAttributes extends DeviceAttributes = DeviceAttributes, TNotifications extends DeviceNotifications = NoDeviceNotifications, TConfig extends AnyDeviceConfig = NoDeviceConfig, diff --git a/src/device/protocol/deviceProtocol.ts b/src/device/protocol/deviceProtocol.ts index e4019fbb..af22518a 100644 --- a/src/device/protocol/deviceProtocol.ts +++ b/src/device/protocol/deviceProtocol.ts @@ -15,17 +15,21 @@ export type MessageWithResponse = Message & { responseType: R | undefined; }; -export type MessageWithOptionalResponse = Message | MessageWithResponse; +type MessageWithOptionalResponse = Message | MessageWithResponse; -export type InferMR

= P extends DeviceProtocol> ? T : never; +export type AnyMessageWithOptionalResponse = MessageWithOptionalResponse; +export type AnyMessageWithResponse = MessageWithResponse; +export type AnyDeviceProtocol = DeviceProtocol; + +export type InferMR

= P extends DeviceProtocol> ? T : never; export type InferMessage = MR extends MessageWithResponse ? M : MR extends Message ? M : never; export type InferResponse = MR extends MessageWithResponse ? R - : MR extends Message ? undefined : never; + : MR extends Message ? undefined : never; -type DeviceProtocol> = { +type DeviceProtocol = { encode(message: InferMessage): Buffer; decode(data: Buffer): DecodeResult>; isResponseMatchingMessage(response: InferResponse, message: MR): boolean; diff --git a/src/device/protocol/messageResponseHandler.ts b/src/device/protocol/messageResponseHandler.ts index 70078009..cdafcf21 100644 --- a/src/device/protocol/messageResponseHandler.ts +++ b/src/device/protocol/messageResponseHandler.ts @@ -1,4 +1,4 @@ -import DeviceProtocol, { InferMR, InferResponse, MessageWithOptionalResponse, MessageWithResponse } from './deviceProtocol.js'; +import DeviceProtocol, { InferMR, InferResponse, AnyMessageWithResponse, AnyMessageWithOptionalResponse } from './deviceProtocol.js'; import DeviceBidirectionalTransport from '../transport/deviceBidirectionalTransport.js'; import { clearTimeout } from 'node:timers'; import Logger from '../../logging/Logger.js'; @@ -13,16 +13,21 @@ type PendingEntry = { pendingSince: number; }; -export default class MessageResponseHandler

>> +type AnyDeviceProtocol = DeviceProtocol; + +// intersection makes P's inferred message/response type resolvable here (opaque P alone can't) +type TypedProtocol

= DeviceProtocol> & P; + +export default class MessageResponseHandler

{ - private readonly protocol: P; + private readonly protocol: TypedProtocol

; private readonly transport: DeviceBidirectionalTransport; private readonly logger: Logger; private readonly pendingEntries = new Set>>(); private readonly timeoutMs: number; - public static create

>>( - protocol: P, + public static create

( + protocol: TypedProtocol

, transport: DeviceBidirectionalTransport, logger: Logger, timeoutMs = 200, @@ -31,7 +36,7 @@ export default class MessageResponseHandler

, transport: DeviceBidirectionalTransport, logger: Logger, timeoutMs: number, @@ -81,7 +86,7 @@ export default class MessageResponseHandler

>( msg: T, - ): msg is Extract> { + ): msg is Extract { return 'responseType' in msg; } diff --git a/src/device/protocol/slvCtrlPlus/slvCtrlPlusDevice.ts b/src/device/protocol/slvCtrlPlus/slvCtrlPlusDevice.ts index 0b01aa38..b2bd85a8 100644 --- a/src/device/protocol/slvCtrlPlus/slvCtrlPlusDevice.ts +++ b/src/device/protocol/slvCtrlPlus/slvCtrlPlusDevice.ts @@ -10,7 +10,8 @@ import EventEmitter from 'events'; import Logger from '../../../logging/Logger.js'; import { DeviceId } from '../../deviceId.js'; -export type SlvCtrlPlusDeviceAttributeKey = string; +type SlvCtrlPlusDeviceAttributeKey = string; + export type SlvCtrlPlusDeviceAttributes = Record; @Exclude() diff --git a/src/device/protocol/virtual/audio/piperVirtualDeviceConfig.ts b/src/device/protocol/virtual/audio/piperVirtualDeviceConfig.ts index 42210814..87489daf 100644 --- a/src/device/protocol/virtual/audio/piperVirtualDeviceConfig.ts +++ b/src/device/protocol/virtual/audio/piperVirtualDeviceConfig.ts @@ -5,5 +5,6 @@ export const piperVirtualDeviceConfigSchema = Type.Object({ model: Type.String(), }); -export type PiperVirtualDeviceConfigSchema = typeof piperVirtualDeviceConfigSchema; +type PiperVirtualDeviceConfigSchema = typeof piperVirtualDeviceConfigSchema; + export type PiperVirtualDeviceConfig = Static; diff --git a/src/device/protocol/virtual/audio/ttsVirtualDeviceConfig.ts b/src/device/protocol/virtual/audio/ttsVirtualDeviceConfig.ts index ad30d153..08683cb6 100644 --- a/src/device/protocol/virtual/audio/ttsVirtualDeviceConfig.ts +++ b/src/device/protocol/virtual/audio/ttsVirtualDeviceConfig.ts @@ -1,8 +1,9 @@ import { Type, Static } from '@sinclair/typebox'; +type TtsVirtualDeviceConfigSchema = typeof ttsVirtualDeviceConfigSchema; + export const ttsVirtualDeviceConfigSchema = Type.Object({ voice: Type.Optional(Type.String()), }); -export type TtsVirtualDeviceConfigSchema = typeof ttsVirtualDeviceConfigSchema; export type TtsVirtualDeviceConfig = Static; diff --git a/src/device/protocol/virtual/audio/ttsVirtualDeviceLogic.ts b/src/device/protocol/virtual/audio/ttsVirtualDeviceLogic.ts index a048e009..7a51225a 100644 --- a/src/device/protocol/virtual/audio/ttsVirtualDeviceLogic.ts +++ b/src/device/protocol/virtual/audio/ttsVirtualDeviceLogic.ts @@ -74,7 +74,7 @@ export default class TtsVirtualDeviceLogic extends VirtualDeviceLogic< } device.setAttribute('speaking', false) - .catch((e: any) => this.logger.error('Could not set attribute "speaking" to false', e)); + .catch((e: unknown) => this.logger.error('Could not set attribute "speaking" to false', e)); }); await device.setAttribute('queueLength', Int.from(this.ttsEntries.length)); } diff --git a/src/device/protocol/virtual/genericVirtualDeviceFactory.ts b/src/device/protocol/virtual/genericVirtualDeviceFactory.ts index 993d3d23..c94a34b2 100644 --- a/src/device/protocol/virtual/genericVirtualDeviceFactory.ts +++ b/src/device/protocol/virtual/genericVirtualDeviceFactory.ts @@ -1,17 +1,15 @@ import { Static, TObject } from '@sinclair/typebox'; -import VirtualDeviceLogic from './virtualDeviceLogic.js'; +import { AnyVirtualDeviceLogic, ExtractConfig } from './virtualDeviceLogic.js'; import DateFactory from '../../../factory/dateFactory.js'; import JsonSchemaValidatorFactory from '../../../schemaValidation/JsonSchemaValidatorFactory.js'; import KnownDevice from '../../../settings/knownDevice.js'; -import VirtualDevice from './virtualDevice.js'; +import VirtualDevice, { AnyVirtualDevice } from './virtualDevice.js'; import VirtualDeviceFactory from './virtualDeviceFactory.js'; import VirtualDeviceLogicFactory from './virtualDeviceLogicFactory.js'; import Logger from '../../../logging/Logger.js'; import EventEmitterFactory from '../../../factory/eventEmitterFactory.js'; -type ExtractConfig> = T extends VirtualDeviceLogic ? C : never; - -type LogicFactoryAndConfigTuple, TConfigSchema extends TObject> = { +type LogicFactoryAndConfigTuple = { deviceLogicFactory: VirtualDeviceLogicFactory; deviceConfigSchema: TConfigSchema & ( Static extends ExtractConfig @@ -29,7 +27,7 @@ export default class GenericVirtualDeviceFactory implements VirtualDeviceFactory private readonly jsonSchemaValidatorFactory: JsonSchemaValidatorFactory; - private readonly logicFactories = new Map, TObject>>(); + private readonly logicFactories = new Map>(); private readonly logger: Logger; @@ -46,7 +44,7 @@ export default class GenericVirtualDeviceFactory implements VirtualDeviceFactory } public addLogicFactory< - TLogic extends VirtualDeviceLogic, + TLogic extends AnyVirtualDeviceLogic, TConfigSchema extends TObject, >( virtualDeviceLogicFactory: LogicFactoryAndConfigTuple['deviceLogicFactory'], @@ -60,8 +58,8 @@ export default class GenericVirtualDeviceFactory implements VirtualDeviceFactory return this; } - public create(knownDevice: KnownDevice, provider: string): Promise> { - return new Promise>(resolve => { + public create(knownDevice: KnownDevice, provider: string): Promise { + return new Promise(resolve => { const factoryName = `${GenericVirtualDeviceFactory.capitalizeFirstLetter(knownDevice.type)}VirtualDeviceLogic`; const factory = this.logicFactories.get(factoryName); diff --git a/src/device/protocol/virtual/genericVirtualDeviceLogicFactory.ts b/src/device/protocol/virtual/genericVirtualDeviceLogicFactory.ts index f0dc659f..5ffc9a6d 100644 --- a/src/device/protocol/virtual/genericVirtualDeviceLogicFactory.ts +++ b/src/device/protocol/virtual/genericVirtualDeviceLogicFactory.ts @@ -1,12 +1,11 @@ -import VirtualDeviceLogic from './virtualDeviceLogic.js'; +import VirtualDeviceLogic, { ExtractConfig } from './virtualDeviceLogic.js'; import Logger from '../../../logging/Logger.js'; import VirtualDeviceLogicFactory from './virtualDeviceLogicFactory.js'; -type ExtractConfig> = T extends VirtualDeviceLogic ? C : never; -type Constructor> = new (config: ExtractConfig, logger: Logger) => TDeviceLogic; +type Constructor = new (config: ExtractConfig, logger: Logger) => TDeviceLogic; export default class GenericVirtualDeviceLogicFactory< - TDeviceLogic extends VirtualDeviceLogic, + TDeviceLogic extends VirtualDeviceLogic, > implements VirtualDeviceLogicFactory { private readonly ctor: Constructor; @@ -18,7 +17,7 @@ export default class GenericVirtualDeviceLogicFactory< this.logger = logger; } - public static from>( + public static from( genericVirtualDeviceLogicLogicConstructor: Constructor, logger: Logger, ): GenericVirtualDeviceLogicFactory { diff --git a/src/device/protocol/virtual/randomGenerator/randomGeneratorVirtualDeviceConfig.ts b/src/device/protocol/virtual/randomGenerator/randomGeneratorVirtualDeviceConfig.ts index d82062cd..f7298bb3 100644 --- a/src/device/protocol/virtual/randomGenerator/randomGeneratorVirtualDeviceConfig.ts +++ b/src/device/protocol/virtual/randomGenerator/randomGeneratorVirtualDeviceConfig.ts @@ -7,5 +7,6 @@ export const randomGeneratorVirtualDeviceConfigSchema = Type.Object({ additionalProperties: false, }); -export type RandomGeneratorVirtualDeviceConfigSchema = typeof randomGeneratorVirtualDeviceConfigSchema; +type RandomGeneratorVirtualDeviceConfigSchema = typeof randomGeneratorVirtualDeviceConfigSchema; + export type RandomGeneratorVirtualDeviceConfig = Static; diff --git a/src/device/protocol/virtual/virtualDevice.ts b/src/device/protocol/virtual/virtualDevice.ts index 2feddb1b..ad5b7218 100644 --- a/src/device/protocol/virtual/virtualDevice.ts +++ b/src/device/protocol/virtual/virtualDevice.ts @@ -2,15 +2,19 @@ import { Exclude, Expose } from 'class-transformer'; import BaseError from 'modern-errors'; import Device, { AttributeKeyOf, AttributeValueOf, NoDeviceNotifications } from '../../device.js'; import DeviceState from '../../deviceState.js'; -import VirtualDeviceLogic, { ExtractAttributes, ExtractConfig } from './virtualDeviceLogic.js'; -import { AnyDeviceConfig } from '../../deviceConfig.js'; +import VirtualDeviceLogic, { AnyVirtualDeviceLogic, ExtractAttributes, ExtractConfig } from './virtualDeviceLogic.js'; import EventEmitter from 'events'; import Logger from '../../../logging/Logger.js'; import { DeviceId } from '../../deviceId.js'; +export type AnyVirtualDevice = VirtualDevice; + +// intersection makes TLogic's extracted attributes/config resolvable here (opaque TLogic alone can't) +type TypedDeviceLogic = VirtualDeviceLogic, ExtractConfig> & TLogic; + @Exclude() export default class VirtualDevice< - TLogic extends VirtualDeviceLogic, + TLogic extends AnyVirtualDeviceLogic, > extends Device, NoDeviceNotifications, ExtractConfig> { @Expose() private deviceModel: string; @@ -18,7 +22,7 @@ export default class VirtualDevice< @Expose() private readonly fwVersion: string; - private readonly deviceLogic: TLogic; + private readonly deviceLogic: TypedDeviceLogic; private readonly statusUpdater?: NodeJS.Timeout; @@ -30,7 +34,7 @@ export default class VirtualDevice< provider: string, connectedSince: Date, config: ExtractConfig, - deviceLogic: TLogic, + deviceLogic: TypedDeviceLogic, eventEmitter: EventEmitter, logger: Logger, ) { diff --git a/src/device/protocol/virtual/virtualDeviceFactory.ts b/src/device/protocol/virtual/virtualDeviceFactory.ts index a99f3fc4..8d1a0584 100644 --- a/src/device/protocol/virtual/virtualDeviceFactory.ts +++ b/src/device/protocol/virtual/virtualDeviceFactory.ts @@ -1,8 +1,8 @@ import KnownDevice from '../../../settings/knownDevice.js'; -import VirtualDevice from './virtualDevice.js'; +import { AnyVirtualDevice } from './virtualDevice.js'; type VirtualDeviceFactory = { - create(knownDevice: KnownDevice, provider: string): Promise>; + create(knownDevice: KnownDevice, provider: string): Promise; }; export default VirtualDeviceFactory; diff --git a/src/device/protocol/virtual/virtualDeviceLogic.ts b/src/device/protocol/virtual/virtualDeviceLogic.ts index 5977c00d..d6aac694 100644 --- a/src/device/protocol/virtual/virtualDeviceLogic.ts +++ b/src/device/protocol/virtual/virtualDeviceLogic.ts @@ -3,15 +3,17 @@ import { DeviceAttributes } from '../../device.js'; import { AnyDeviceConfig, NoDeviceConfig } from '../../deviceConfig.js'; export type ExtractAttributes> = - TLogic extends VirtualDeviceLogic + TLogic extends VirtualDeviceLogic ? TAttributes : never; export type ExtractConfig> = - TLogic extends VirtualDeviceLogic + TLogic extends VirtualDeviceLogic ? TConfig : never; +export type AnyVirtualDeviceLogic = VirtualDeviceLogic; + export default abstract class VirtualDeviceLogic< TAttributes extends DeviceAttributes = DeviceAttributes, TConfig extends AnyDeviceConfig = NoDeviceConfig, diff --git a/src/device/protocol/virtual/virtualDeviceLogicFactory.ts b/src/device/protocol/virtual/virtualDeviceLogicFactory.ts index 4f9a2130..b06a91e1 100644 --- a/src/device/protocol/virtual/virtualDeviceLogicFactory.ts +++ b/src/device/protocol/virtual/virtualDeviceLogicFactory.ts @@ -1,8 +1,6 @@ -import VirtualDeviceLogic from './virtualDeviceLogic.js'; +import VirtualDeviceLogic, { ExtractConfig } from './virtualDeviceLogic.js'; -type ExtractConfig> = T extends VirtualDeviceLogic ? C : never; - -type VirtualDeviceLogicFactory> = { +type VirtualDeviceLogicFactory = { create(config: ExtractConfig): TDeviceLogic; forDeviceType(): string; diff --git a/src/device/protocol/virtual/virtualDeviceProvider.ts b/src/device/protocol/virtual/virtualDeviceProvider.ts index 3d722c65..e92cf31b 100644 --- a/src/device/protocol/virtual/virtualDeviceProvider.ts +++ b/src/device/protocol/virtual/virtualDeviceProvider.ts @@ -1,6 +1,6 @@ import DeviceProvider from '../../provider/deviceProvider.js'; import Logger from '../../../logging/Logger.js'; -import VirtualDevice from './virtualDevice.js'; +import { AnyVirtualDevice } from './virtualDevice.js'; import KnownDevice from '../../../settings/knownDevice.js'; import SettingsManager from '../../../settings/settingsManager.js'; import SettingsEventType from '../../../settings/settingsEventType.js'; @@ -17,7 +17,7 @@ export type VirtualDeviceDetectionInfo = DeviceDetectionInfo & { knownDevice: KnownDevice; }; -export default class VirtualDeviceProvider extends DeviceProvider> +export default class VirtualDeviceProvider extends DeviceProvider { public static readonly providerName = 'virtual'; @@ -57,7 +57,7 @@ export default class VirtualDeviceProvider extends DeviceProvider> { + protected override createDevice(deviceDetectionInfo: VirtualDeviceDetectionInfo): Promise { this.logger.info(`Virtual device detected: ${deviceDetectionInfo.knownDevice.name}`, deviceDetectionInfo.knownDevice); return this.deviceFactory.create(deviceDetectionInfo.knownDevice, VirtualDeviceProvider.providerName); diff --git a/src/device/protocol/zc95/zc95Protocol.ts b/src/device/protocol/zc95/zc95Protocol.ts index 62d0b188..ed8a1af3 100644 --- a/src/device/protocol/zc95/zc95Protocol.ts +++ b/src/device/protocol/zc95/zc95Protocol.ts @@ -15,7 +15,7 @@ export type MsgResponse = { Error?: string; }; -export type ResponseIdentifier = { +type ResponseIdentifier = { msgId: number; type: ResponseToKey; }; diff --git a/src/device/provider/deviceProviderEvent.ts b/src/device/provider/deviceProviderEvent.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/device/provider/genericDeviceProviderFactory.ts b/src/device/provider/genericDeviceProviderFactory.ts index 74f87b1c..07d51720 100644 --- a/src/device/provider/genericDeviceProviderFactory.ts +++ b/src/device/provider/genericDeviceProviderFactory.ts @@ -1,19 +1,24 @@ import { AnyDeviceProvider } from './deviceProvider.js'; import DeviceProviderFactory from './deviceProviderFactory.js'; -type ConcreteCtor = new (...args: any[]) => T; +type TypedCtor = new (...args: never[]) => T; -export default class GenericDeviceProviderFactory implements DeviceProviderFactory +// intersection makes T's instance type resolvable here (opaque T alone can't) +type ResolvedCtor> = TypedCtor> & T; + +export default class GenericDeviceProviderFactory< + T extends TypedCtor, +> implements DeviceProviderFactory> { - private readonly ctor: ConcreteCtor; - private readonly args: ConstructorParameters>; + private readonly ctor: ResolvedCtor; + private readonly args: ConstructorParameters; - public constructor(ctor: ConcreteCtor, ...args: ConstructorParameters>) { + public constructor(ctor: ResolvedCtor, ...args: ConstructorParameters) { this.ctor = ctor; this.args = args; } - public create(): DP { + public create(): InstanceType { return new this.ctor(...this.args); } } diff --git a/src/device/serializedTypes.ts b/src/device/serializedTypes.ts index 79ffb4e4..e38d8628 100644 --- a/src/device/serializedTypes.ts +++ b/src/device/serializedTypes.ts @@ -9,7 +9,7 @@ type SerializedDeviceAttributeBase = { type: string; }; -export type SerializedIntRangeDeviceAttribute = SerializedDeviceAttributeBase & { +type SerializedIntRangeDeviceAttribute = SerializedDeviceAttributeBase & { type: 'range'; value: number | undefined; min: number; @@ -18,35 +18,35 @@ export type SerializedIntRangeDeviceAttribute = SerializedDeviceAttributeBase & uom: string | undefined; }; -export type SerializedIntDeviceAttribute = SerializedDeviceAttributeBase & { +type SerializedIntDeviceAttribute = SerializedDeviceAttributeBase & { type: 'int'; value: number | undefined; uom: string | undefined; }; -export type SerializedFloatDeviceAttribute = SerializedDeviceAttributeBase & { +type SerializedFloatDeviceAttribute = SerializedDeviceAttributeBase & { type: 'float'; value: number | undefined; uom: string | undefined; }; -export type SerializedBoolDeviceAttribute = SerializedDeviceAttributeBase & { +type SerializedBoolDeviceAttribute = SerializedDeviceAttributeBase & { type: 'bool'; value: boolean | undefined; }; -export type SerializedStrDeviceAttribute = SerializedDeviceAttributeBase & { +type SerializedStrDeviceAttribute = SerializedDeviceAttributeBase & { type: 'str'; value: string | undefined; }; -export type SerializedListDeviceAttribute = SerializedDeviceAttributeBase & { +type SerializedListDeviceAttribute = SerializedDeviceAttributeBase & { type: 'list'; value: string | number | undefined; values: { key: string | number, value: string | number }[]; }; -export type SerializedDeviceAttribute = +type SerializedDeviceAttribute = | SerializedIntRangeDeviceAttribute | SerializedIntDeviceAttribute | SerializedFloatDeviceAttribute @@ -67,30 +67,30 @@ type SerializedDeviceBase = { config: Record; }; -export type SerializedSlvCtrlPlusDevice = SerializedDeviceBase & { +type SerializedSlvCtrlPlusDevice = SerializedDeviceBase & { type: 'slvCtrlPlus'; deviceModel: string; fwVersion: number; protocolVersion: number; }; -export type SerializedButtplugIoDevice = SerializedDeviceBase & { +type SerializedButtplugIoDevice = SerializedDeviceBase & { type: 'buttplugIo'; deviceModel: string; }; -export type SerializedVirtualDevice = SerializedDeviceBase & { +type SerializedVirtualDevice = SerializedDeviceBase & { type: 'virtual'; deviceModel: string; fwVersion: string; }; -export type SerializedZc95Device = SerializedDeviceBase & { +type SerializedZc95Device = SerializedDeviceBase & { type: 'zc95'; fwVersion: string; }; -export type SerializedEStim2bDevice = SerializedDeviceBase & { +type SerializedEStim2bDevice = SerializedDeviceBase & { type: 'estim2b'; fwVersion: string; }; diff --git a/src/serialization/discriminator/genericDeviceAttributeDiscriminator.ts b/src/serialization/discriminator/genericDeviceAttributeDiscriminator.ts deleted file mode 100644 index 8fbcccee..00000000 --- a/src/serialization/discriminator/genericDeviceAttributeDiscriminator.ts +++ /dev/null @@ -1,18 +0,0 @@ -import ObjectDiscriminator from './objectDiscriminator.js'; -import BoolDeviceAttribute from '../../device/attribute/boolDeviceAttribute.js'; -import IntDeviceAttribute from '../../device/attribute/intDeviceAttribute.js'; -import FloatDeviceAttribute from '../../device/attribute/floatDeviceAttribute.js'; -import StrDeviceAttribute from '../../device/attribute/strDeviceAttribute.js'; -import IntRangeDeviceAttribute from '../../device/attribute/intRangeDeviceAttribute.js'; -import ListDeviceAttribute from '../../device/attribute/listDeviceAttribute.js'; - -const genericDeviceAttributeDiscriminator = new ObjectDiscriminator([ - { value: BoolDeviceAttribute, name: 'bool' }, - { value: IntDeviceAttribute, name: 'int' }, - { value: FloatDeviceAttribute, name: 'float' }, - { value: StrDeviceAttribute, name: 'str' }, - { value: IntRangeDeviceAttribute, name: 'range' }, - { value: ListDeviceAttribute, name: 'list' }, -]); - -export default genericDeviceAttributeDiscriminator; diff --git a/src/serialization/discriminator/objectDiscriminator.ts b/src/serialization/discriminator/objectDiscriminator.ts index 9bc47969..a67d62a2 100644 --- a/src/serialization/discriminator/objectDiscriminator.ts +++ b/src/serialization/discriminator/objectDiscriminator.ts @@ -4,7 +4,7 @@ type DiscriminatorMap = { // Name of the type. name: string; // A class constructor which can be used to create the object. - value: ClassConstructor; + value: ClassConstructor; }[]; export default class ObjectDiscriminator { @@ -14,7 +14,7 @@ export default class ObjectDiscriminator { this.discriminatorMap = discriminatorMap; } - public fromName(name: string): ClassConstructor { + public fromName(name: string): ClassConstructor { for (const subType of this.discriminatorMap) { if (subType.name === name) { return subType.value; @@ -24,7 +24,7 @@ export default class ObjectDiscriminator { throw new Error(`Could not resolve from name '${name}'`); } - public fromValue(value: ClassConstructor): string { + public fromValue(value: ClassConstructor): string { for (const subType of this.discriminatorMap) { if (subType.value === value) { return subType.name; diff --git a/src/serviceProvider/deviceServiceProvider.ts b/src/serviceProvider/deviceServiceProvider.ts index 4895a432..d9a9234a 100644 --- a/src/serviceProvider/deviceServiceProvider.ts +++ b/src/serviceProvider/deviceServiceProvider.ts @@ -71,13 +71,11 @@ export default class DeviceServiceProvider implements ServiceProvider { - return new DeviceManager( - container.get('factory.eventEmitter').create(), - container.get('settings.manager'), - container.get('logger.default'), - ); - }); + container.set('device.manager', (): DeviceManager => new DeviceManager( + container.get('factory.eventEmitter').create(), + container.get('settings.manager'), + container.get('logger.default'), + )); container.set('device.uniqueNameGenerator', () => { const config: Config = { @@ -178,84 +176,72 @@ export default class DeviceServiceProvider implements ServiceProvider { - return new DeviceProviderManager( - new Map>([ - [ - SlvCtrlPlusSerialDeviceProvider.providerName, - container.get('device.provider.factory.slvCtrlPlusSerial'), - ], - [ - ButtplugIoWebsocketDeviceProvider.providerName, - container.get('device.provider.factory.buttplugIoWebsocket'), - ], - [ - VirtualDeviceProvider.providerName, - container.get('device.provider.factory.virtual'), - ], - [ - Zc95SerialDeviceProvider.providerName, - container.get('device.provider.factory.zc95Serial'), - ], - [ - EStim2bSerialDeviceProvider.providerName, - container.get('device.provider.factory.estim2bSerial'), - ], - [ - AiroticDeviceProvider.providerName, - container.get('device.provider.factory.airotic'), - ], - ]), - container.get('logger.default'), - ); - }); + container.set('device.provider.manager', (): DeviceProviderManager => new DeviceProviderManager( + new Map>([ + [ + SlvCtrlPlusSerialDeviceProvider.providerName, + container.get('device.provider.factory.slvCtrlPlusSerial'), + ], + [ + ButtplugIoWebsocketDeviceProvider.providerName, + container.get('device.provider.factory.buttplugIoWebsocket'), + ], + [ + VirtualDeviceProvider.providerName, + container.get('device.provider.factory.virtual'), + ], + [ + Zc95SerialDeviceProvider.providerName, + container.get('device.provider.factory.zc95Serial'), + ], + [ + EStim2bSerialDeviceProvider.providerName, + container.get('device.provider.factory.estim2bSerial'), + ], + [ + AiroticDeviceProvider.providerName, + container.get('device.provider.factory.airotic'), + ], + ]), + container.get('logger.default'), + )); - container.set('device.provider.factory.zc95Serial', () => { - return new GenericDeviceProviderFactory( - Zc95SerialDeviceProvider, - container.get('device.manager'), - container.get('factory.serialPort'), - container.get('device.observer.serial'), - container.get('device.serial.transport.factory'), - container.get('device.factory.zc95'), - container.get('logger.default'), - ); - }); + container.set('device.provider.factory.zc95Serial', () => new GenericDeviceProviderFactory( + Zc95SerialDeviceProvider, + container.get('device.manager'), + container.get('factory.serialPort'), + container.get('device.observer.serial'), + container.get('device.serial.transport.factory'), + container.get('device.factory.zc95'), + container.get('logger.default'), + )); - container.set('device.provider.factory.estim2bSerial', () => { - return new GenericDeviceProviderFactory( - EStim2bSerialDeviceProvider, - container.get('device.manager'), - container.get('factory.serialPort'), - container.get('device.observer.serial'), - container.get('device.serial.transport.factory'), - container.get('device.factory.estim2b'), - container.get('logger.default'), - ); - }); + container.set('device.provider.factory.estim2bSerial', () => new GenericDeviceProviderFactory( + EStim2bSerialDeviceProvider, + container.get('device.manager'), + container.get('factory.serialPort'), + container.get('device.observer.serial'), + container.get('device.serial.transport.factory'), + container.get('device.factory.estim2b'), + container.get('logger.default'), + )); - container.set('device.provider.factory.airotic', () => { - return new GenericDeviceProviderFactory( - AiroticDeviceProvider, - container.get('device.manager'), - container.get('device.observer.ble'), - container.get('device.factory.airotic'), - container.get('logger.default'), - ); - }); + container.set('device.provider.factory.airotic', () => new GenericDeviceProviderFactory( + AiroticDeviceProvider, + container.get('device.manager'), + container.get('device.observer.ble'), + container.get('device.factory.airotic'), + container.get('logger.default'), + )); - container.set('device.observer.serial', () => { - return new SerialPortObserver( - container.get('device.manager'), - container.get('logger.default'), - ); - }); + container.set('device.observer.serial', () => new SerialPortObserver( + container.get('device.manager'), + container.get('logger.default'), + )); - container.set('device.observer.ble', () => { - return new BleObserver( - container.get('device.manager'), - container.get('logger.default'), - ); - }); + container.set('device.observer.ble', () => new BleObserver( + container.get('device.manager'), + container.get('logger.default'), + )); } } diff --git a/src/settings/serializedTypes.ts b/src/settings/serializedTypes.ts index c762548a..cee56a58 100644 --- a/src/settings/serializedTypes.ts +++ b/src/settings/serializedTypes.ts @@ -1,6 +1,6 @@ import { JsonObject } from '../types.js'; -export type SerializedKnownDevice = { +type SerializedKnownDevice = { id: string; serialNo: string; name: string; @@ -10,7 +10,7 @@ export type SerializedKnownDevice = { enabled: boolean; }; -export type SerializedDeviceSource = { +type SerializedDeviceSource = { id: string; type: string; config: JsonObject; diff --git a/src/socket/types.ts b/src/socket/types.ts index d6ceda38..649f0391 100644 --- a/src/socket/types.ts +++ b/src/socket/types.ts @@ -1,5 +1,5 @@ import { Server } from 'socket.io'; -import { DeviceData, DeviceNotification } from '../device/device.js'; +import { DeviceData, AnyDeviceNotification } from '../device/device.js'; import WebSocketEvent from '../device/webSocketEvent.js'; import SettingsEventType from '../settings/settingsEventType.js'; import AutomationEventType from '../automation/automationEventType.js'; @@ -18,7 +18,7 @@ export type ServerToClientEvents = { [WebSocketEvent.deviceConnected]: (data: SerializedDevice) => void; [WebSocketEvent.deviceDisconnected]: (data: SerializedDevice) => void; [WebSocketEvent.deviceRefreshed]: (data: SerializedDevice) => void; - [WebSocketEvent.deviceNotification]: (device: SerializedDevice, notification: DeviceNotification) => void; + [WebSocketEvent.deviceNotification]: (device: SerializedDevice, notification: AnyDeviceNotification) => void; [WebSocketEvent.healthMetrics]: (data: SerializedHealthMetrics) => void; [SettingsEventType.changed]: (data: SerializedSettings) => void; [AutomationEventType.consoleLog]: (data: string) => void; diff --git a/src/util/async.ts b/src/util/async.ts index 45bf7ed8..9a529550 100644 --- a/src/util/async.ts +++ b/src/util/async.ts @@ -7,7 +7,7 @@ export class IntervalTimeoutError extends Error { } } -export const setImmediateInterval = ( +export const setImmediateInterval = ( callback: (...args: TArgs) => void, delay?: number, ...args: TArgs @@ -34,7 +34,7 @@ export const asyncHandler = ( export type IntervalAsync = { clear: () => void }; -export const setIntervalAsync = ( +export const setIntervalAsync = ( fn: (...args: TArgs) => Promise, options: IntervalAsyncOptions, ...args: TArgs diff --git a/src/util/devNullStream.ts b/src/util/devNullStream.ts index be9af42f..2a9fabc9 100644 --- a/src/util/devNullStream.ts +++ b/src/util/devNullStream.ts @@ -1,6 +1,7 @@ import { Writable } from 'stream'; -export default class DevNullStream extends Writable { +export default class DevNullStream extends Writable +{ private readonly timeoutMs: number; private timer?: NodeJS.Timeout; @@ -10,7 +11,7 @@ export default class DevNullStream extends Writable { this.resetTimer(); } - public override _write(chunk: any, encoding: string, callback: (error?: Error | null) => void): void { + public override _write(chunk: unknown, encoding: string, callback: (error?: Error | null) => void): void { this.resetTimer(); callback(); } diff --git a/src/util/dictionary.ts b/src/util/dictionary.ts index 2fe0a9e0..e8f99793 100644 --- a/src/util/dictionary.ts +++ b/src/util/dictionary.ts @@ -1,4 +1,4 @@ -export const starWarsPlanets: string[] = [ +const starWarsPlanets: string[] = [ 'Alderaan', 'Bespin', 'Batuu', @@ -25,7 +25,7 @@ export const starWarsPlanets: string[] = [ 'Yavin', ]; -export const starWarsSpecies: string[] = [ +const starWarsSpecies: string[] = [ 'Jawa', 'Ewok', 'Gungan', diff --git a/tests/integration/helpers/airoticDeviceSimulator.ts b/tests/integration/helpers/airoticDeviceSimulator.ts index 16149134..8a25c087 100644 --- a/tests/integration/helpers/airoticDeviceSimulator.ts +++ b/tests/integration/helpers/airoticDeviceSimulator.ts @@ -150,7 +150,7 @@ export class AiroticDeviceSimulator { // Tests import this to trigger BLE discovery events. // --------------------------------------------------------------------------- -export class MockNoble extends EventEmitter { +class MockNoble extends EventEmitter { public async waitForPoweredOnAsync(): Promise { // no-op: BLE always "powered on" in tests } diff --git a/tests/integration/helpers/appHelper.ts b/tests/integration/helpers/appHelper.ts index b333980d..6e9825b5 100644 --- a/tests/integration/helpers/appHelper.ts +++ b/tests/integration/helpers/appHelper.ts @@ -143,41 +143,6 @@ export const resetTestApp = async (app: TestApp): Promise => { } }; -export function getConnectedDevice( - container: Container, - predicate: (device: AnyDevice) => boolean, - description: string, -): AnyDevice { - const device = container.get('device.manager').getConnectedDevices().find(predicate); - if (undefined === device) { - throw new Error(`No connected device found: ${description}`); - } - return device; -} - -export function waitForNDevicesConnected(container: Container, deviceCount: number, timeoutMs = 5000): Promise { - return new Promise((resolve, reject) => { - const deviceManager = container.get('device.manager'); - const connected: AnyDevice[] = []; - - const timeout = setTimeout(() => { - deviceManager.off(DeviceManagerEvent.deviceConnected, listener); - reject(new Error(`Timed out waiting for ${deviceCount} device(s) to connect (>${timeoutMs}ms), got ${connected.length}`)); - }, timeoutMs); - - const listener = (device: AnyDevice): void => { - connected.push(device); - if (connected.length >= deviceCount) { - clearTimeout(timeout); - deviceManager.off(DeviceManagerEvent.deviceConnected, listener); - resolve(connected); - } - }; - - deviceManager.on(DeviceManagerEvent.deviceConnected, listener); - }); -} - export function waitForNextWsEvent( wsEmitSpy: { mock: { calls: ReadonlyArray } }, event: E, @@ -205,7 +170,7 @@ export function waitForNextWsEvent( }); } -export const getServerPort = (server: http.Server): number => { +const getServerPort = (server: http.Server): number => { const address = server.address(); if (address !== null && typeof address === 'object') { return address.port; diff --git a/tests/integration/helpers/buttplugIoServerSimulator.ts b/tests/integration/helpers/buttplugIoServerSimulator.ts index 93881bf4..4325c32f 100644 --- a/tests/integration/helpers/buttplugIoServerSimulator.ts +++ b/tests/integration/helpers/buttplugIoServerSimulator.ts @@ -5,7 +5,7 @@ import { ActuatorType, SensorType, RequestServerInfo, RequestDeviceList, StartSc /** * Defines a mock actuator on a simulated Buttplug device. */ -export interface MockActuator { +interface MockActuator { featureDescriptor: string; actuatorType: ActuatorType; stepCount: number; @@ -15,7 +15,7 @@ export interface MockActuator { * Defines a mock sensor on a simulated Buttplug device. * sensorRange is [min, max]. */ -export interface MockSensor { +interface MockSensor { featureDescriptor: string; sensorType: SensorType; sensorRange: [number, number]; diff --git a/tests/integration/helpers/mockSerialPortFactory.ts b/tests/integration/helpers/mockSerialPortFactory.ts index 1175bff2..271d5716 100644 --- a/tests/integration/helpers/mockSerialPortFactory.ts +++ b/tests/integration/helpers/mockSerialPortFactory.ts @@ -1,6 +1,6 @@ import { SerialPort, SerialPortMock } from 'serialport'; import { SerialPortStream } from '@serialport/stream'; -import { BindingInterface, PortInfo } from '@serialport/bindings-interface'; +import { BindingInterface } from '@serialport/bindings-interface'; import { SerialPortOpenOptions } from 'serialport'; import { AutoDetectTypes } from '@serialport/bindings-cpp'; import SerialPortFactory from '../../../src/factory/serialPortFactory.js'; From c7e945c31411995c894680988194044f361b8352 Mon Sep 17 00:00:00 2001 From: HRS Date: Thu, 6 Aug 2026 00:03:05 +0200 Subject: [PATCH 03/15] Re-add type-detect --- knip.json | 3 ++- package-lock.json | 10 ++++++++++ package.json | 1 + 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/knip.json b/knip.json index cc6652f9..197b4c4b 100644 --- a/knip.json +++ b/knip.json @@ -1,5 +1,6 @@ { "ignoreDependencies": [ - "@serialport/bindings-cpp" + "@serialport/bindings-cpp", + "type-detect" ] } diff --git a/package-lock.json b/package-lock.json index 1de27669..fc3dc035 100644 --- a/package-lock.json +++ b/package-lock.json @@ -32,6 +32,7 @@ "socket.io": "^4.8.3", "speaker": "https://github.com/SlvCtrlPlus/node-speaker/releases/download/v0.1.0/speaker-v0.1.0.tgz", "sucrase": "^3.35.1", + "type-detect": "^4.1.0", "unique-names-generator": "^4.7.1", "usb": "^3.0.0", "uuid": "^14.0.0" @@ -7359,6 +7360,15 @@ "node": ">= 0.8.0" } }, + "node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/type-is": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", diff --git a/package.json b/package.json index 0d131d1b..2e819a49 100644 --- a/package.json +++ b/package.json @@ -30,6 +30,7 @@ "socket.io": "^4.8.3", "speaker": "https://github.com/SlvCtrlPlus/node-speaker/releases/download/v0.1.0/speaker-v0.1.0.tgz", "sucrase": "^3.35.1", + "type-detect": "^4.1.0", "unique-names-generator": "^4.7.1", "usb": "^3.0.0", "uuid": "^14.0.0" From 60a37f364e64c3dd4cf38b9cf22179ec70f5feab Mon Sep 17 00:00:00 2001 From: HRS Date: Fri, 7 Aug 2026 08:50:15 +0200 Subject: [PATCH 04/15] Make eslint and typescript even stricterer --- .github/workflows/release.yml | 1 - eslint.config.ts | 58 ++++--------------- resources/schemas/settings.schema.json | 40 ------------- src/app.ts | 21 +++---- src/automation/scriptRuntime.ts | 8 +-- src/automation/scriptVmFactory.ts | 22 +++++-- src/device/attribute/deviceAttribute.ts | 5 ++ src/device/attribute/listDeviceAttribute.ts | 2 +- src/device/attribute/strDeviceAttribute.ts | 2 +- src/device/detectedDeviceOfferQueue.ts | 2 +- src/device/device.ts | 2 +- src/device/deviceId.ts | 4 +- src/device/deviceManager.ts | 3 +- src/device/knownDeviceRegistry.ts | 2 +- src/device/peripheralDevice.ts | 2 +- src/device/protocol/airotic/airoticDevice.ts | 37 +++++++++--- .../protocol/airotic/airoticDeviceFactory.ts | 6 +- .../protocol/buttplugIo/buttplugIoDevice.ts | 31 +++++++--- .../buttplugIo/buttplugIoDeviceFactory.ts | 15 +++-- .../buttplugIoWebsocketDeviceProvider.ts | 4 +- ...trlPlusButtplugWebsocketClientConnector.ts | 2 +- src/device/protocol/estim2b/estim2bDevice.ts | 22 +++---- .../protocol/estim2b/estim2bDeviceFactory.ts | 4 +- .../protocol/estim2b/estim2bProtocol.ts | 23 ++++++-- .../estim2b/estim2bSerialDeviceProvider.ts | 7 +-- src/device/protocol/messageResponseHandler.ts | 9 +-- .../slvCtrlPlus/genericSlvCtrlPlusDevice.ts | 2 +- .../slvCtrlPlus/slvCtrlPlusDeviceFactory.ts | 11 +++- .../slvCtrlPlusSerialDeviceProvider.ts | 6 +- .../slvCtrlPlus/slvCtrlProtocolLegacy.ts | 19 +++--- .../protocol/slvCtrlPlus/slvCtrlProtocolV1.ts | 22 +++++-- .../virtual/audio/piperVirtualDeviceLogic.ts | 37 +++++++----- .../virtual/audio/ttsVirtualDeviceLogic.ts | 6 +- src/device/protocol/virtual/virtualDevice.ts | 7 ++- .../protocol/virtual/virtualDeviceProvider.ts | 4 +- src/device/protocol/zc95/zc95Device.ts | 19 +++--- src/device/protocol/zc95/zc95Protocol.ts | 33 +++++++---- .../protocol/zc95/zc95SerialDeviceProvider.ts | 12 ++-- src/device/provider/deviceProviderManager.ts | 4 +- src/device/provider/serialDeviceProvider.ts | 6 +- src/device/transport/bleDeviceTransport.ts | 8 +-- src/device/transport/bleObserver.ts | 12 ++-- .../transport/deviceReadableTransport.ts | 2 +- .../transport/deviceWritableTransport.ts | 2 +- src/device/transport/serialDeviceTransport.ts | 4 +- src/device/transport/serialPortObserver.ts | 8 +-- src/factory/serialPortFactory.ts | 3 +- src/health/healthMetricsCollector.ts | 20 +++---- src/logging/Logger.ts | 4 +- src/logging/PinoLogger.ts | 6 +- .../JsonSchemaValidatorFactory.ts | 6 -- src/serial/synchronousSerialPort.ts | 12 ++-- src/serialization/classToPlainSerializer.ts | 3 +- src/serialization/plainToClassSerializer.ts | 2 +- .../automationServiceProvider.ts | 2 +- src/serviceProvider/deviceServiceProvider.ts | 1 + .../repositoryServiceProvider.ts | 2 +- .../settingsServiceProvider.ts | 2 +- src/settings/settingsManager.ts | 8 +-- src/util/expressUtils.ts | 4 +- src/util/json.ts | 25 ++++++++ src/util/typeUtils.ts | 11 ++++ tests/integration/automationScripts.spec.ts | 2 +- tests/integration/deviceEvents.spec.ts | 6 +- tests/integration/helpers/appHelper.ts | 8 ++- .../helpers/estim2bDeviceSimulator.ts | 4 +- .../helpers/zc95DeviceSimulator.ts | 6 +- tests/unit/automation/scriptRuntime.spec.ts | 10 +++- .../slvCtrlPlus/slvCtrlProtocolLegacy.spec.ts | 32 +++++----- .../slvCtrlPlus/slvCtrlProtocolV1.spec.ts | 36 ++++++------ .../device/protocol/zc95/zc95Device.spec.ts | 7 ++- .../protocol/zc95/zc95DeviceFactory.spec.ts | 7 ++- .../device/protocol/zc95/zc95Protocol.spec.ts | 8 ++- tests/unit/serial/frameParser.spec.ts | 12 ++-- tsconfig.json | 1 + 75 files changed, 450 insertions(+), 358 deletions(-) delete mode 100644 resources/schemas/settings.schema.json create mode 100644 src/util/json.ts create mode 100644 src/util/typeUtils.ts diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0165f476..773f2dc4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -51,7 +51,6 @@ jobs: files: | ./dist ./node_modules - ./resources outPath: dist.tar.gz - name: Create Release Notes File env: diff --git a/eslint.config.ts b/eslint.config.ts index 704a0292..927416c1 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -19,8 +19,8 @@ export default [ ], }, eslint.configs.recommended, - ...tseslint.configs.strict, - ...tseslint.configs.stylistic, + ...tseslint.configs.strictTypeChecked, + ...tseslint.configs.stylisticTypeChecked, stylistic.configs.recommended, preferArrowFunctions.configs.all, { @@ -37,17 +37,19 @@ export default [ "@jsdoc": jsdoc, }, rules: { + "@typescript-eslint/no-confusing-void-expression": [ + "error", + { + "ignoreArrowShorthand": true, + } + ], // your overrides - "@typescript-eslint/adjacent-overload-signatures": "error", "@typescript-eslint/array-type": [ "error", { "default": "array" } ], - "@typescript-eslint/no-empty-object-type": "error", - "@typescript-eslint/no-unsafe-function-type": "error", - "@typescript-eslint/no-wrapper-object-types": "error", "@typescript-eslint/no-restricted-types": [ "error", { @@ -90,9 +92,7 @@ export default [ "leadingUnderscore": "allow" } ], - "@typescript-eslint/no-empty-function": "error", "@typescript-eslint/no-empty-interface": "off", - "@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/no-floating-promises": [ "warn", { @@ -101,47 +101,24 @@ export default [ ], "@typescript-eslint/unbound-method": "error", "@typescript-eslint/no-misused-promises": "error", - "@typescript-eslint/no-misused-new": "error", - "@typescript-eslint/no-namespace": "error", - "@typescript-eslint/no-extraneous-class": "error", - "@typescript-eslint/no-invalid-void-type": "error", "@typescript-eslint/consistent-type-definitions": [ "error", "type" ], - "@typescript-eslint/no-parameter-properties": "off", - "@typescript-eslint/no-shadow": [ - "error", - { - "hoist": "all" - } - ], + "@typescript-eslint/parameter-properties": "error", + "@typescript-eslint/no-shadow": "error", "@typescript-eslint/no-unused-expressions": "error", - "@typescript-eslint/no-use-before-define": "off", - "@typescript-eslint/no-require-imports": "error", - "@typescript-eslint/prefer-for-of": "error", - "@typescript-eslint/prefer-function-type": "error", - "@typescript-eslint/prefer-namespace-keyword": "error", + "@typescript-eslint/no-use-before-define": "error", "@typescript-eslint/restrict-template-expressions": [ "warn", { "allowNumber": true } ], - "@typescript-eslint/triple-slash-reference": [ - "error", - { - "path": "always", - "types": "prefer-import", - "lib": "always" - } - ], "@typescript-eslint/typedef": "off", - "@typescript-eslint/unified-signatures": "error", "@typescript-eslint/strict-boolean-expressions": "error", "@stylistic/indent": ["error", 4], "@stylistic/semi": ["error", "always"], - "@stylistic/comma-dangle": ["error", "always-multiline"], "@stylistic/brace-style": "off", "@stylistic/member-delimiter-style": [ "error", @@ -176,7 +153,6 @@ export default [ "@jsdoc/check-indentation": "error", "@jsdoc/tag-lines": "error", "complexity": "error", - "constructor-super": "error", "dot-notation": "off", "eqeqeq": [ "error", @@ -196,17 +172,10 @@ export default [ "undefined" ], "id-match": "error", - "max-classes-per-file": [ - "error", - 1 - ], + "max-classes-per-file": "error", "no-bitwise": "error", "no-caller": "error", - "no-cond-assign": "error", "no-console": "warn", - "no-debugger": "error", - "no-empty": "error", - "no-empty-function": "off", "no-eval": "error", "no-fallthrough": "off", "no-invalid-this": "off", @@ -220,9 +189,7 @@ export default [ "allowAfterThis": true } ], - "no-unsafe-finally": "error", "no-unused-expressions": "error", - "no-unused-labels": "error", "no-use-before-define": "off", "no-implicit-coercion": ["error", { "boolean": true, @@ -237,7 +204,6 @@ export default [ ], "prefer-const": "error", "radix": "error", - "use-isnan": "error", "valid-typeof": "off", }, }, diff --git a/resources/schemas/settings.schema.json b/resources/schemas/settings.schema.json deleted file mode 100644 index f0a743d2..00000000 --- a/resources/schemas/settings.schema.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "knownDevices": { - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "id": { "type": "string", "format": "uuid" }, - "serialNo": { "type": "string" }, - "name": { "type": "string" }, - "type": { "type": "string" }, - "source": { "type": "string" }, - "config": { "type": "object" } - }, - "required": ["id", "name", "type", "source"], - "additionalProperties": false - } - }, - "deviceSources": { - "type": "object", - "additionalProperties": { - "type": "object", - "properties": { - "id": { "type": "string", "format": "uuid" }, - "type": { "type": "string" }, - "config": { - "type": "object", - "additionalProperties": true - } - }, - "required": ["id", "type", "config"], - "additionalProperties": false - } - } - }, - "required": ["knownDevices", "deviceSources"], - "additionalProperties": false -} diff --git a/src/app.ts b/src/app.ts index c02da3a6..ea16d6b0 100644 --- a/src/app.ts +++ b/src/app.ts @@ -110,22 +110,22 @@ const configureWebsocket = (io: WebsocketServer, container: Container { io.emit(WebSocketEvent.deviceConnected, serializer.transform(device, deviceDiscriminatorInstance)); - void scriptRuntime.runForEvent({ type: DeviceManagerEvent.deviceConnected, device, args: [] }); + scriptRuntime.runForEvent({ type: DeviceManagerEvent.deviceConnected, device, args: [] }); }); deviceManager.on(DeviceManagerEvent.deviceDisconnected, (device: AnyDevice) => { io.emit(WebSocketEvent.deviceDisconnected, serializer.transform(device, deviceDiscriminatorInstance)); - void scriptRuntime.runForEvent({ type: DeviceManagerEvent.deviceDisconnected, device, args: [] }); + scriptRuntime.runForEvent({ type: DeviceManagerEvent.deviceDisconnected, device, args: [] }); }); deviceManager.on(DeviceManagerEvent.deviceRefreshed, (device: AnyDevice) => { io.emit(WebSocketEvent.deviceRefreshed, serializer.transform(device, deviceDiscriminatorInstance)); - void scriptRuntime.runForEvent({ type: DeviceManagerEvent.deviceRefreshed, device, args: [] }); + scriptRuntime.runForEvent({ type: DeviceManagerEvent.deviceRefreshed, device, args: [] }); }); deviceManager.on(DeviceManagerEvent.deviceNotification, (device: AnyDevice, notification) => { io.emit(WebSocketEvent.deviceNotification, serializer.transform(device, deviceDiscriminatorInstance), notification); - void scriptRuntime.runForEvent({ type: DeviceManagerEvent.deviceNotification, device, args: [notification] }); + scriptRuntime.runForEvent({ type: DeviceManagerEvent.deviceNotification, device, args: [notification] }); }); settingsManager.on(SettingsEventType.changed, (settings: Settings) => { @@ -145,24 +145,25 @@ const startDeviceProviders = (container: Container): void => { deviceProviderManager .loadFromSettings(settings) - .catch(e => logError(logger, `Loading device providers failed`, e)); + .catch((e: unknown) => logError(logger, `Loading device providers failed`, e)); settingsManager.on(SettingsEventType.changed, (changedSettings: Settings) => { // Reload device sources first so re-enabled devices are only re-announced once their provider runs again deviceProviderManager .loadFromSettings(changedSettings) - .catch(e => logError(logger, 'Failed to reload device sources after settings change', e)) + .catch((e: unknown) => logError(logger, 'Failed to reload device sources after settings change', e)) .then(() => deviceManager.onSettingsChanged()) - .catch(e => logError(logger, 'Failed to apply device enabled/disabled changes', e)); + .catch((e: unknown) => logError(logger, 'Failed to apply device enabled/disabled changes', e)); }); }; const buildCorsOptions = (allowedOrigins: string[]): CorsOptions => ({ origin: (origin, callback): void => { if (undefined === origin || allowedOrigins.length === 0) { - return callback(null, true); + callback(null, true); + return; } - return callback(null, allowedOrigins.includes(origin)); + callback(null, allowedOrigins.includes(origin)); }, }); @@ -182,7 +183,7 @@ export const createContainer = (dataPath: string): Pimple => (new Pi const getPortFromServer = (server: http.Server): number => { const address = server.address(); - if (address === null || address === undefined || typeof address !== 'object') { + if (address === null || typeof address !== 'object') { throw new Error('Could not obtain server address'); } diff --git a/src/automation/scriptRuntime.ts b/src/automation/scriptRuntime.ts index 9bab72ca..5ba65e02 100644 --- a/src/automation/scriptRuntime.ts +++ b/src/automation/scriptRuntime.ts @@ -65,7 +65,7 @@ export default class ScriptRuntime }); this.vm = vm; } catch (e) { - this.logWriter?.destroy(); + this.logWriter.destroy(); this.logWriter = null; throw e; } @@ -90,7 +90,7 @@ export default class ScriptRuntime { // isRunning() (runningSince !== null) and vm !== null are set/cleared together in // load()/this block below, so this also guarantees vm below is non-null. - if (this.isRunning() === false || this.vm === null) { + if (!this.isRunning() || this.vm === null) { return; } @@ -148,9 +148,7 @@ export default class ScriptRuntime return vm.dispatchEvent(event.type, deviceToBridgeJson(event.device), event.args); }); - if (this.processQueuePromise === null) { - this.processQueuePromise = this.processQueue(); - } + this.processQueuePromise ??= this.processQueue(); } private async processQueue(): Promise diff --git a/src/automation/scriptVmFactory.ts b/src/automation/scriptVmFactory.ts index 2a23c34b..d2739375 100644 --- a/src/automation/scriptVmFactory.ts +++ b/src/automation/scriptVmFactory.ts @@ -211,10 +211,10 @@ export default class ScriptVmFactory trace: msg => this.automationScriptLogger.trace(msg), }; - await jail.set(VM_REF_LOG, new ivm.Reference((level: string, msg: string) => { - const str = String(msg); - (loggerMethods[level] ?? this.automationScriptLogger.info.bind(this.automationScriptLogger))(str); - onConsoleLog(str); + await jail.set(VM_REF_LOG, new ivm.Reference((level: string, msg: unknown) => { + const msgStr = String(msg); + (loggerMethods[level] ?? this.automationScriptLogger.info.bind(this.automationScriptLogger))(msgStr); + onConsoleLog(msgStr); })); await jail.set(VM_REF_GET_ATTRIBUTE, new ivm.Reference(async (deviceId: string, attrName: string): Promise => { @@ -256,8 +256,14 @@ export default class ScriptVmFactory await compiledBootstrap.run(vmContext); await compiledScript.run(vmContext, { promise: true }); - const dispatchRef: ivm.Reference = await vmContext.global.get(VM_REF_DISPATCH_EVENT); - const lifecycleRef: ivm.Reference = await vmContext.global.get(VM_REF_DISPATCH_LIFECYCLE); + const dispatchRef: unknown = await vmContext.global.get(VM_REF_DISPATCH_EVENT, { reference: true }); + const lifecycleRef: unknown = await vmContext.global.get(VM_REF_DISPATCH_LIFECYCLE, { reference: true }); + + if (!this.isIvmReference(dispatchRef)) { + throw new Error(`Expected '${VM_REF_DISPATCH_EVENT}' to be an ivm.Reference`); + } else if (!this.isIvmReference(lifecycleRef)) { + throw new Error(`Expected '${VM_REF_DISPATCH_LIFECYCLE}' to be an ivm.Reference`); + } return new ScriptVm(isolate, vmContext, dispatchRef, lifecycleRef, signals); } catch (e) { @@ -266,4 +272,8 @@ export default class ScriptVmFactory throw e; } } + + private isIvmReference(obj: unknown): obj is ivm.Reference { + return typeof obj === 'object' && obj !== null && 'applySync' in obj && typeof obj.applySync === 'function'; + } } diff --git a/src/device/attribute/deviceAttribute.ts b/src/device/attribute/deviceAttribute.ts index 3915e568..2f0b581a 100644 --- a/src/device/attribute/deviceAttribute.ts +++ b/src/device/attribute/deviceAttribute.ts @@ -12,6 +12,11 @@ export enum DeviceAttributeModifier writeOnly = 'wo', } +export const isValidAttributeValue = ( + attribute: DeviceAttribute | undefined, + value: unknown, +): value is NotUndefined => attribute?.isValidValue(value) ?? false; + @Exclude() export default abstract class DeviceAttribute { diff --git a/src/device/attribute/listDeviceAttribute.ts b/src/device/attribute/listDeviceAttribute.ts index b9962fe1..c64b9d80 100644 --- a/src/device/attribute/listDeviceAttribute.ts +++ b/src/device/attribute/listDeviceAttribute.ts @@ -57,7 +57,7 @@ export default class ListDeviceAttribute< } public fromString(value: string): V { - if (this._values.length === 0 || typeof this._values[0].key === 'string') { + if (this._values.length === 0 || typeof this._values[0]?.key === 'string') { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions return value as V; } diff --git a/src/device/attribute/strDeviceAttribute.ts b/src/device/attribute/strDeviceAttribute.ts index 38d71117..70c8a7db 100644 --- a/src/device/attribute/strDeviceAttribute.ts +++ b/src/device/attribute/strDeviceAttribute.ts @@ -19,7 +19,7 @@ export default class StrDeviceAttribute { - return attr !== null && typeof attr === 'object' && 'name' in attr && Object.keys(this.attributes).includes(attr.name); + return typeof attr === 'object' && 'name' in attr && Object.keys(this.attributes).includes(attr.name); } } diff --git a/src/device/deviceId.ts b/src/device/deviceId.ts index 361f541f..12e67ab6 100644 --- a/src/device/deviceId.ts +++ b/src/device/deviceId.ts @@ -10,7 +10,7 @@ export type DetectionId = string & { [detectionIdSymbol]: never }; export const DeviceId = { create: (seed: string): DeviceId => { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return uuidv5(seed, DEVICE_NAMESPACE).toString() as DeviceId; + return uuidv5(seed, DEVICE_NAMESPACE) as DeviceId; }, fromDetectionId: (detectionId: DetectionId): DeviceId => { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions @@ -21,7 +21,7 @@ export const DeviceId = { export const DetectionId = { create: (seed: string): DetectionId => { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - return uuidv5(seed, DEVICE_NAMESPACE).toString() as DetectionId; + return uuidv5(seed, DEVICE_NAMESPACE) as DetectionId; }, fromDeviceId: (deviceId: DeviceId): DetectionId => { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions diff --git a/src/device/deviceManager.ts b/src/device/deviceManager.ts index 7d95fb18..a77b5ff0 100644 --- a/src/device/deviceManager.ts +++ b/src/device/deviceManager.ts @@ -9,6 +9,7 @@ import { DeviceId, DetectionId } from './deviceId.js'; import SettingsManager from '../settings/settingsManager.js'; import DeviceOfferRejectedError from './deviceOfferRejectedError.js'; import DetectedDeviceOfferQueue, { OfferResult } from './detectedDeviceOfferQueue.js'; +import { normalizeError } from '../util/typeUtils.js'; export type DeviceDetectionInfo = { type: string; @@ -257,7 +258,7 @@ export default class DeviceManager this.detectedDisabledDevices.clear(); if (undefined !== closeError) { - throw closeError; + throw normalizeError(closeError); } } diff --git a/src/device/knownDeviceRegistry.ts b/src/device/knownDeviceRegistry.ts index f36084a9..726dd4a8 100644 --- a/src/device/knownDeviceRegistry.ts +++ b/src/device/knownDeviceRegistry.ts @@ -21,7 +21,7 @@ export default class KnownDeviceRegistry public resolve(deviceId: DeviceId, type: string, provider: string, name?: string): KnownDevice { const knownDevice = this.settings.getKnownDeviceById(deviceId); - if (undefined !== knownDevice && knownDevice.type === type) { + if (knownDevice?.type === type) { // Already known (previously detected serial number) this.logger.debug(`Device is already known: ${knownDevice.id}`); return knownDevice; diff --git a/src/device/peripheralDevice.ts b/src/device/peripheralDevice.ts index acd83545..75bdc900 100644 --- a/src/device/peripheralDevice.ts +++ b/src/device/peripheralDevice.ts @@ -38,7 +38,7 @@ export default abstract class PeripheralDevice< this.protocol = protocol; this.transport = transport; - this.transport.onClose(async () => { + this.transport.onClose(() => { this.close().catch((err: unknown) => logError(this.logger, 'Error closing device after transport close', err)); }); } diff --git a/src/device/protocol/airotic/airoticDevice.ts b/src/device/protocol/airotic/airoticDevice.ts index 8c83d26a..2c031490 100644 --- a/src/device/protocol/airotic/airoticDevice.ts +++ b/src/device/protocol/airotic/airoticDevice.ts @@ -15,6 +15,7 @@ import { sleep } from '../../../util/async.js'; import BleUartDeviceTransport from '../../transport/bleDeviceTransport.js'; import { Float } from '../../../util/numbers.js'; import typeDetect from 'type-detect'; +import { hasExactLength } from '../../../util/typeUtils.js'; const BREATH_WINDOW_MS = 60_000; const BREATH_TIMEOUT_MS = 20_000; @@ -41,7 +42,7 @@ export type AiroticDeviceNotifications = { }; @Exclude() -export default class AiroticDevice extends BleDevice +export default class AiroticDevice extends BleDevice { private readonly messageResponseHandler: MessageResponseHandler; @@ -123,8 +124,11 @@ export default class AiroticDevice extends BleDevice 0 && this.breathTimestamps[0] < cutoff) { + let first = this.breathTimestamps[0]; + + while (first !== undefined && first < cutoff) { this.breathTimestamps.shift(); + first = this.breathTimestamps[0]; } if (this.breathTimestamps.length < 2) { @@ -132,7 +136,14 @@ export default class AiroticDevice extends BleDevice, V extends AttributeValueOf, >(attributeName: K, value: V): Promise { - if (attributeName === 'restColor' && value !== null && typeof value === 'string') { + if (attributeName === 'restColor' && value !== undefined && typeof value === 'string') { const { r, g, b } = this.parseColor(value); await this.messageResponseHandler.send(AiroticProtocol.createSelectRestColorMessage()); await sleep(100); @@ -184,7 +201,7 @@ export default class AiroticDevice extends BleDevice; type AttributeValue = AttributeValueOf; @@ -56,8 +56,10 @@ export default class ButtplugIoDevice extends Device this.buttplugClientDevice.on('deviceremoved', this.deviceRemovedHandler); } - protected override async doClose(): Promise { + protected override doClose(): Promise { this.buttplugClientDevice.off('deviceremoved', this.deviceRemovedHandler); + + return Promise.resolve(); } public override get getRefreshInterval(): number | undefined { @@ -69,7 +71,14 @@ export default class ButtplugIoDevice extends Device protected override async doRefresh(): Promise { for (const sensor of this.buttplugClientDevice.messageAttributes.SensorReadCmd ?? []) { const value = await this.buttplugClientDevice.sensorRead(sensor.Index, sensor.SensorType); - this.attributes[`${sensor.SensorType}-${sensor.Index}`].value = Int.from(value[0]); + const attr = this.attributes[`${sensor.SensorType}-${sensor.Index}`]; + if (undefined !== attr) { + if (undefined === value[0] || isNaN(value[0])) { + this.logger.warn(`Received invalid sensor value for sensor type '${sensor.SensorType}' and index '${sensor.Index}': ${JSON.stringify(value)}. Supposed to be a number. Ignoring this value.`); + continue; + } + attr.value = Int.from(value[0]); + } } } @@ -104,25 +113,33 @@ export default class ButtplugIoDevice extends Device const [actuatorType, index] = attributeName.split('-'); - if (!this.isActuatorTypeKey(actuatorType)) { + if (!this.isActuatorTypeKey(actuatorType) || undefined === index) { throw new Error(`Attribute with name '${attributeName}' does not correspond to a valid actuator type key`); } await this.send(actuatorType, parseInt(index, 10), valueToSend); - this.attributes[`${attributeName}`].value = value; + const attr = this.attributes[attributeName]; + + if (undefined !== attr) { + attr.value = value; + } return value; } - private isActuatorTypeKey(key: string): key is ActuatorType { + private isActuatorTypeKey(key: string | undefined): key is ActuatorType { + if (undefined === key) { + return false; + } + const actuatorValueSet: (ActuatorType | string)[] = Object.values(ActuatorType); return actuatorValueSet.includes(key); } protected async send(command: ActuatorType, index: number, value: number): Promise { - return await this.buttplugClientDevice.scalar({ + await this.buttplugClientDevice.scalar({ ActuatorType: command, Scalar: value, Index: index, diff --git a/src/device/protocol/buttplugIo/buttplugIoDeviceFactory.ts b/src/device/protocol/buttplugIo/buttplugIoDeviceFactory.ts index fac55729..612d4a8c 100644 --- a/src/device/protocol/buttplugIo/buttplugIoDeviceFactory.ts +++ b/src/device/protocol/buttplugIo/buttplugIoDeviceFactory.ts @@ -52,10 +52,6 @@ export default class ButtplugIoDeviceFactory this.logger, ); - if (null === device) { - throw new Error('Unknown device type: ' + knownDevice.name); - } - this.knownDeviceRegistry.persist(knownDevice); return device; @@ -91,13 +87,20 @@ export default class ButtplugIoDeviceFactory // A range is defined by two numbers, if there are more or less, let's fallback // to a normal integer attribute. Not that dramatic for a sensor after all. if ('SensorRange' in item && Array.isArray(item.SensorRange) && item.SensorRange.length === 2) { + const lowerBound: unknown = item.SensorRange[0]; + const upperBound: unknown = item.SensorRange[1]; + + if (typeof lowerBound !== 'number' || typeof upperBound !== 'number') { + throw new Error(`Sensor range for sensor type '${item.SensorType}' and index '${item.Index}' is not a valid number range`); + } + attributes[attrName] = IntRangeDeviceAttribute.createInitialized( `${item.SensorType}-${item.Index}`, item.FeatureDescriptor, DeviceAttributeModifier.readOnly, undefined, - Int.from(item.SensorRange[0]), - Int.from(item.SensorRange[1]), + Int.from(lowerBound), + Int.from(upperBound), Int.from(1), Int.ZERO, ); diff --git a/src/device/protocol/buttplugIo/buttplugIoWebsocketDeviceProvider.ts b/src/device/protocol/buttplugIo/buttplugIoWebsocketDeviceProvider.ts index dc3fb735..28db9126 100644 --- a/src/device/protocol/buttplugIo/buttplugIoWebsocketDeviceProvider.ts +++ b/src/device/protocol/buttplugIo/buttplugIoWebsocketDeviceProvider.ts @@ -62,7 +62,7 @@ export default class ButtplugIoWebsocketDeviceProvider extends DeviceProvider< this.buttplugClient = new ButtplugClient('SlvCtrlPlus'); } - protected override async doStart(): Promise { + protected override doStart(): Promise { const url = `ws://${this.websocketAddress}/buttplug`; this.buttplugClient.on('disconnect', asyncHandler( @@ -73,6 +73,8 @@ export default class ButtplugIoWebsocketDeviceProvider extends DeviceProvider< this.buttplugClient.on('deviceremoved', this.revokePendingButtplugIoDevice.bind(this)); this.connectClient(); + + return Promise.resolve(); } protected override async doStop(): Promise { diff --git a/src/device/protocol/buttplugIo/slvCtrlPlusButtplugWebsocketClientConnector.ts b/src/device/protocol/buttplugIo/slvCtrlPlusButtplugWebsocketClientConnector.ts index e6722cdd..c536b126 100644 --- a/src/device/protocol/buttplugIo/slvCtrlPlusButtplugWebsocketClientConnector.ts +++ b/src/device/protocol/buttplugIo/slvCtrlPlusButtplugWebsocketClientConnector.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-misused-promises */ +/* eslint-disable */ import { ButtplugNodeWebsocketClientConnector } from 'buttplug'; // This is needed to make try/catch around connect() work until upgraded to buttplug@4.0.0 diff --git a/src/device/protocol/estim2b/estim2bDevice.ts b/src/device/protocol/estim2b/estim2bDevice.ts index 88171979..dca7c7b8 100644 --- a/src/device/protocol/estim2b/estim2bDevice.ts +++ b/src/device/protocol/estim2b/estim2bDevice.ts @@ -6,7 +6,7 @@ import { Int } from '../../../util/numbers.js'; import BoolDeviceAttribute from '../../attribute/boolDeviceAttribute.js'; import StrDeviceAttribute from '../../attribute/strDeviceAttribute.js'; import ListDeviceAttribute from '../../attribute/listDeviceAttribute.js'; -import { DeviceAttributeModifier } from '../../attribute/deviceAttribute.js'; +import { DeviceAttributeModifier, isValidAttributeValue } from '../../attribute/deviceAttribute.js'; import DeviceBidirectionalTransport from '../../transport/deviceBidirectionalTransport.js'; import PeripheralDevice from '../../peripheralDevice.js'; import { getErrorFromDecodeResult } from '../deviceProtocol.js'; @@ -104,26 +104,18 @@ export default class EStim2bDevice extends PeripheralDevice { + ): Estim2bDevice { const attributes = this.getAttributes(initialStatus); const knownDevice = this.knownDeviceRegistry.resolve(DeviceId.fromDetectionId(detectionId), 'estim2b', provider); diff --git a/src/device/protocol/estim2b/estim2bProtocol.ts b/src/device/protocol/estim2b/estim2bProtocol.ts index 0c970f07..492fa5a5 100644 --- a/src/device/protocol/estim2b/estim2bProtocol.ts +++ b/src/device/protocol/estim2b/estim2bProtocol.ts @@ -1,3 +1,4 @@ +import { hasExactLength } from '../../../util/typeUtils.js'; import DeviceProtocol, { DecodeResult, InferMessage, MessageWithResponse } from '../deviceProtocol.js'; export type EStim2bStatus = { @@ -6,8 +7,8 @@ export type EStim2bStatus = { channelBLevel: number; pulseFrequency: number; pulsePwm: number; - currentMode: number; - powerMode: string; + currentMode: EStim2bMode; + powerMode: EStim2PowerMode; channelsJoined: boolean; firmwareVersion: string; }; @@ -57,8 +58,10 @@ export type EStim2bProtocolMessage = MessageWithResponse { + private static isEStim2PowerMode = (value: string): value is EStim2PowerMode => 'H' === value || 'L' === value; + public encode(message: InferMessage): Buffer { - return Buffer.from(`${message}`, 'utf-8'); + return Buffer.from(message, 'utf-8'); } public decode(data: Buffer): DecodeResult { @@ -150,7 +153,7 @@ export default class EStim2bProtocol implements DeviceProtocol { const parts = response.split(':'); - if (9 !== parts.length) { + if (!hasExactLength(parts, 9)) { return { error: { type: 'invalid_frame', @@ -165,6 +168,7 @@ export default class EStim2bProtocol implements DeviceProtocol): Promise { + protected async connectSerialDevice(deviceDetectionInfo: SerialDeviceDetectionInfo, port: SerialPortStream): Promise { const parser = port.pipe(new ReadlineParser({ delimiter: '\n' })); const syncPort = new SynchronousSerialPort(deviceDetectionInfo.portInfo, parser, port, this.logger); const transport = this.transportFactory.create(syncPort, undefined, Buffer.from('\r')); @@ -53,15 +52,13 @@ export default class EStim2bSerialDeviceProvider extends SerialDeviceProvider = { msg: MR; @@ -47,7 +48,7 @@ export default class MessageResponseHandler

this.timeoutMs = timeoutMs; transport.onReceive(data => this.onResponse(data)); - transport.onClose(async () => { + transport.onClose(() => { for (const entry of this.pendingEntries) { clearTimeout(entry.timeout); entry.reject(new Error('Transport closed before a response was received')); @@ -97,7 +98,7 @@ export default class MessageResponseHandler

const encodedMsg = this.protocol.encode(msg.message); const realTimeoutMs = timeoutMs ?? this.timeoutMs; - if (false === this.isMessageWithResponse(msg)) { + if (!this.isMessageWithResponse(msg)) { return promiseWithTimeout(new Promise>((resolve, reject) => { this.transport.send(encodedMsg).then(() => { // eslint-disable-next-line @typescript-eslint/consistent-type-assertions @@ -123,10 +124,10 @@ export default class MessageResponseHandler

this.pendingEntries.add(entry); - this.transport.send(encodedMsg).catch(error => { + this.transport.send(encodedMsg).catch((error: unknown) => { clearTimeout(entry.timeout); this.pendingEntries.delete(entry); - reject(error); + reject(normalizeError(error)); }); }); } diff --git a/src/device/protocol/slvCtrlPlus/genericSlvCtrlPlusDevice.ts b/src/device/protocol/slvCtrlPlus/genericSlvCtrlPlusDevice.ts index 1dbc9794..94c4bb31 100644 --- a/src/device/protocol/slvCtrlPlus/genericSlvCtrlPlusDevice.ts +++ b/src/device/protocol/slvCtrlPlus/genericSlvCtrlPlusDevice.ts @@ -60,7 +60,7 @@ export default class GenericSlvCtrlPlusDevice extends SlvCtrlPlusDevice continue; } - attribute.value = ('' !== response.data[attrKey]) ? attribute.fromString(response.data[attrKey]) : undefined; + attribute.value = (undefined !== response.data[attrKey] && '' !== response.data[attrKey]) ? attribute.fromString(response.data[attrKey]) : undefined; } } diff --git a/src/device/protocol/slvCtrlPlus/slvCtrlPlusDeviceFactory.ts b/src/device/protocol/slvCtrlPlus/slvCtrlPlusDeviceFactory.ts index 84140ffb..ef16324b 100644 --- a/src/device/protocol/slvCtrlPlus/slvCtrlPlusDeviceFactory.ts +++ b/src/device/protocol/slvCtrlPlus/slvCtrlPlusDeviceFactory.ts @@ -79,9 +79,14 @@ export default class SlvCtrlPlusDeviceFactory } const deviceInfo = decodedInfoResponse.message.data; + const { fw, protocol: protocolVersionRaw, type: deviceType } = deviceInfo; - const fwVersion = Number.parseInt(deviceInfo.fw, 10); - const protocolVersion = Number.parseInt(deviceInfo.protocol, 10); + if (undefined === fw || undefined === protocolVersionRaw || undefined === deviceType) { + throw new Error(`Missing required device info fields: fw='${fw}', protocol='${protocolVersionRaw}', type='${deviceType}'`); + } + + const fwVersion = Number.parseInt(fw, 10); + const protocolVersion = Number.parseInt(protocolVersionRaw, 10); if (Number.isNaN(fwVersion) || Number.isNaN(protocolVersion)) { throw new Error( @@ -89,7 +94,7 @@ export default class SlvCtrlPlusDeviceFactory ); } - return { fwVersion, protocolVersion, deviceType: deviceInfo.type, protocol }; + return { fwVersion, protocolVersion, deviceType, protocol }; } private async getAttributes(transport: DeviceBidirectionalTransport, protocol: SlvCtrlProtocol): Promise diff --git a/src/device/protocol/slvCtrlPlus/slvCtrlPlusSerialDeviceProvider.ts b/src/device/protocol/slvCtrlPlus/slvCtrlPlusSerialDeviceProvider.ts index 17f06c22..31064bc6 100644 --- a/src/device/protocol/slvCtrlPlus/slvCtrlPlusSerialDeviceProvider.ts +++ b/src/device/protocol/slvCtrlPlus/slvCtrlPlusSerialDeviceProvider.ts @@ -1,6 +1,6 @@ import { ReadlineParser, ReadyParser } from 'serialport'; import { SerialPortStream } from '@serialport/stream'; -import { BindingInterface, PortInfo } from '@serialport/bindings-interface'; +import { PortInfo } from '@serialport/bindings-interface'; import SlvCtrlPlusDeviceFactory from './slvCtrlPlusDeviceFactory.js'; import SynchronousSerialPort from '../../../serial/synchronousSerialPort.js'; import SerialDeviceTransportFactory from '../../transport/serialDeviceTransportFactory.js'; @@ -39,7 +39,7 @@ export default class SlvCtrlPlusSerialDeviceProvider extends SerialDeviceProvide this.deviceTransportFactory = deviceTransportFactory; } - protected async connectSerialDevice(deviceDetectionInfo: SerialDeviceDetectionInfo, port: SerialPortStream): Promise + protected async connectSerialDevice(deviceDetectionInfo: SerialDeviceDetectionInfo, port: SerialPortStream): Promise { const parser = port.pipe(new ReadlineParser({ delimiter: SlvCtrlProtocol.EOF })); const syncPort = new SynchronousSerialPort(deviceDetectionInfo.portInfo, parser, port, this.logger); @@ -79,7 +79,7 @@ export default class SlvCtrlPlusSerialDeviceProvider extends SerialDeviceProvide return { baudRate: 9600 }; } - protected override preparePort(port: SerialPortStream, portInfo: PortInfo): Promise { + protected override preparePort(port: SerialPortStream, portInfo: PortInfo): Promise { return new Promise((resolve, reject) => { if (portInfo.vendorId !== SlvCtrlPlusSerialDeviceProvider.arduinoVendorId) { // It's NOT an Arduino diff --git a/src/device/protocol/slvCtrlPlus/slvCtrlProtocolLegacy.ts b/src/device/protocol/slvCtrlPlus/slvCtrlProtocolLegacy.ts index b890606a..5567e732 100644 --- a/src/device/protocol/slvCtrlPlus/slvCtrlProtocolLegacy.ts +++ b/src/device/protocol/slvCtrlPlus/slvCtrlProtocolLegacy.ts @@ -12,6 +12,7 @@ import SlvCtrlProtocol, { SlvCtrlProtocolMessage, } from './slvCtrlProtocol.js'; import { DecodeResult, InferMessage, InferResponse } from '../deviceProtocol.js'; +import { hasExactLength } from '../../../util/typeUtils.js'; type SetAttributeResponse = { command: string; @@ -56,9 +57,9 @@ export default class SlvCtrlProtocolLegacy extends SlvCtrlProtocol const keyValuePairs: KeyValuePairs = {}; const unparsedKeyValuePairs = data.split(','); - if (command.startsWith('set-') && unparsedKeyValuePairs.length === 1) { + if (command.startsWith('set-') && hasExactLength(unparsedKeyValuePairs, 1)) { keyValuePairs.value = unparsedKeyValuePairs[0]; - } else if (command === 'introduce' && unparsedKeyValuePairs.length === 3) { + } else if (command === 'introduce' && hasExactLength(unparsedKeyValuePairs, 3)) { keyValuePairs.type = unparsedKeyValuePairs[0]; keyValuePairs.fw = unparsedKeyValuePairs[1]; keyValuePairs.protocol = unparsedKeyValuePairs[2]; @@ -66,7 +67,7 @@ export default class SlvCtrlProtocolLegacy extends SlvCtrlProtocol for (const foo of unparsedKeyValuePairs) { const [key, value] = foo.split(':'); - if (undefined !== key && '' !== key) { + if (undefined !== key && '' !== key && undefined !== value) { keyValuePairs[key] = value; } } @@ -132,7 +133,9 @@ export default class SlvCtrlProtocolLegacy extends SlvCtrlProtocol for (const dataPart of dataParts) { const [key, value] = dataPart.split(SlvCtrlProtocolLegacy.attributeNameValueSeparator); - dataObj[key] = value; + if (undefined !== key && undefined !== value) { + dataObj[key] = value; + } } return dataObj; @@ -141,14 +144,14 @@ export default class SlvCtrlProtocolLegacy extends SlvCtrlProtocol private static parseAttributeSetResponse(response: string): SetAttributeResponse | undefined { const responseParts = response.split(';'); - if (responseParts.length !== 3) { + if (!hasExactLength(responseParts, 3)) { return undefined; } const [command, value, statusTemp] = responseParts; const statusParts = statusTemp.split(':'); - if (statusParts.length !== 2) { + if (!hasExactLength(statusParts, 2)) { return undefined; } @@ -164,7 +167,7 @@ export default class SlvCtrlProtocolLegacy extends SlvCtrlProtocol const reRange = /^(\d+)-(\d+)$/; const reResult = re.exec(definition); - if (null === reResult) { + if (null === reResult || !hasExactLength(reResult, 3)) { return undefined; } @@ -185,7 +188,7 @@ export default class SlvCtrlProtocolLegacy extends SlvCtrlProtocol attr = FloatDeviceAttribute.create(name, undefined, modifier, undefined); } else if ('str' === value) { attr = StrDeviceAttribute.create(name, undefined, modifier); - } else if (null !== (result = reRange.exec(value))) { + } else if (null !== (result = reRange.exec(value)) && hasExactLength(result, 3)) { attr = IntRangeDeviceAttribute.create( name, undefined, diff --git a/src/device/protocol/slvCtrlPlus/slvCtrlProtocolV1.ts b/src/device/protocol/slvCtrlPlus/slvCtrlProtocolV1.ts index 0514a331..74cedfc3 100644 --- a/src/device/protocol/slvCtrlPlus/slvCtrlProtocolV1.ts +++ b/src/device/protocol/slvCtrlPlus/slvCtrlProtocolV1.ts @@ -12,6 +12,7 @@ import SlvCtrlProtocol, { } from './slvCtrlProtocol.js'; import { DecodeResult, InferMessage, InferResponse } from '../deviceProtocol.js'; import { SlvCtrlPlusDeviceAttributes } from './slvCtrlPlusDevice.js'; +import { hasExactLength } from '../../../util/typeUtils.js'; export default class SlvCtrlProtocolV1 extends SlvCtrlProtocol { @@ -54,7 +55,7 @@ export default class SlvCtrlProtocolV1 extends SlvCtrlProtocol const reList = /^(int|str)\(([^|()]+(\|[^|()]+)*)\)$/; const reResult = re.exec(definition); - if (null === reResult) { + if (null === reResult || !hasExactLength(reResult, 3)) { return undefined; } @@ -77,13 +78,20 @@ export default class SlvCtrlProtocolV1 extends SlvCtrlProtocol attr = StrDeviceAttribute.create(name, undefined, modifier); } else if (null !== (result = reRange.exec(value))) { if ('int' === result[1]) { + const min = result[2]; + const max = result[3]; + + if (undefined === min || undefined === max) { + throw new Error(`Malformed range attribute definition: ${value}`); + } + attr = IntRangeDeviceAttribute.create( name, undefined, modifier, undefined, - Int.from(parseInt(result[2], 10)), - Int.from(parseInt(result[3], 10)), + Int.from(parseInt(min, 10)), + Int.from(parseInt(max, 10)), Int.from(1), ); } else if ('float' === result[1]) { @@ -91,7 +99,13 @@ export default class SlvCtrlProtocolV1 extends SlvCtrlProtocol } } else if (null !== (result = reList.exec(value))) { const [, listType, listContent] = result; + + if (undefined === listType || undefined === listContent) { + throw new Error(`Malformed list attribute definition: ${value}`); + } + resultList = listContent.split('|'); + if ('str' === listType) { attr = ListDeviceAttribute.create( name, undefined, modifier, resultList.map(v => ({ key: v, value: v })), @@ -128,7 +142,7 @@ export default class SlvCtrlProtocolV1 extends SlvCtrlProtocol private static parseResponse(response: string): DecodeResult> { const segments = response.split(SlvCtrlProtocolV1.segmentSeparator); - if (segments.length !== 3) { + if (!hasExactLength(segments, 3)) { return { error: { type: 'invalid_frame', reason: `Unexpected segment count (${segments.length})` } }; } diff --git a/src/device/protocol/virtual/audio/piperVirtualDeviceLogic.ts b/src/device/protocol/virtual/audio/piperVirtualDeviceLogic.ts index 3037ece3..10594f8d 100644 --- a/src/device/protocol/virtual/audio/piperVirtualDeviceLogic.ts +++ b/src/device/protocol/virtual/audio/piperVirtualDeviceLogic.ts @@ -12,20 +12,24 @@ import DeviceState from '../../../deviceState.js'; import { PiperVirtualDeviceConfig } from './piperVirtualDeviceConfig.js'; import DevNullStream from '../../../../util/devNullStream.js'; import VirtualDeviceLogic from '../virtualDeviceLogic.js'; +import { Static, Type } from '@sinclair/typebox'; +import { Value } from '@sinclair/typebox/value'; + +const PiperModelMetadataSchema = Type.Object({ + num_speakers: Type.Optional(Type.Number()), + sample_width: Type.Optional(Type.Number()), + audio: Type.Optional(Type.Object({ + sample_rate: Type.Optional(Type.Number()), + })), +}); + +type PiperModelMetadata = Static; type PiperVirtualDeviceAttributes = { text: StrDeviceAttribute; queuing: BoolDeviceAttribute; }; -type PiperModelMetadata = { - num_speakers?: number; - sample_width?: number; - audio?: { - sample_rate?: number; - }; -}; - export default class PiperVirtualDeviceLogic extends VirtualDeviceLogic< PiperVirtualDeviceAttributes, PiperVirtualDeviceConfig @@ -70,7 +74,7 @@ export default class PiperVirtualDeviceLogic extends VirtualDeviceLogic< // If queuing is disabled, we must destroy speaker to end output // and return because we need to wait until the stdout of piper // process is drained (see stopPlayback() for details) - if (false === queuing && this.stopPlayback()) { + if (!queuing && this.stopPlayback()) { return; } @@ -187,14 +191,19 @@ export default class PiperVirtualDeviceLogic extends VirtualDeviceLogic< try { const fileContent = await fs.promises.readFile(metadataFilePath, 'utf-8'); - const json = JSON.parse(fileContent); + const json: unknown = JSON.parse(fileContent); - this.logger.info(`Read metadata for Piper model from '${metadataFilePath}'`); + if (!Value.Check(PiperModelMetadataSchema, json)) { + this.logger.warn(`Metadata file '${metadataFilePath}' has an unexpected shape. Ignoring it.`); + return undefined; + } + + this.logger.info(`Successfully read metadata for Piper model from '${metadataFilePath}'`); return json; } catch (e: unknown) { if (typeof e === 'object' && e !== null && 'code' in e && e.code === 'ENOENT') { - this.logger.warn(`Could not read metadata file '${metadataFilePath}'`); + this.logger.warn(`Could not read Piper model metadata file '${metadataFilePath}'`); return undefined; } @@ -214,7 +223,7 @@ export default class PiperVirtualDeviceLogic extends VirtualDeviceLogic< this.piperProcess.stdout.pipe(this.speaker); } - public override async destroy(): Promise { + public override destroy(): Promise { this.stopPlayback(); if (this.piperProcess !== undefined) { @@ -222,6 +231,8 @@ export default class PiperVirtualDeviceLogic extends VirtualDeviceLogic< this.piperProcess.kill(); this.piperProcess = undefined; } + + return Promise.resolve(); } private stopPlayback(): boolean { diff --git a/src/device/protocol/virtual/audio/ttsVirtualDeviceLogic.ts b/src/device/protocol/virtual/audio/ttsVirtualDeviceLogic.ts index 7a51225a..341765bf 100644 --- a/src/device/protocol/virtual/audio/ttsVirtualDeviceLogic.ts +++ b/src/device/protocol/virtual/audio/ttsVirtualDeviceLogic.ts @@ -40,7 +40,7 @@ export default class TtsVirtualDeviceLogic extends VirtualDeviceLogic< const speaking = (await device.getAttribute('speaking'))?.value ?? false; if (undefined !== text) { - if (false === queuing) { + if (!queuing) { this.ttsEntries = []; } this.ttsEntries.push(text); @@ -52,9 +52,9 @@ export default class TtsVirtualDeviceLogic extends VirtualDeviceLogic< return; } - if (false === queuing) { + if (!queuing) { say.stop(); - } else if (true === speaking) { + } else if (speaking) { return; // already speaking, so don't do anything } diff --git a/src/device/protocol/virtual/virtualDevice.ts b/src/device/protocol/virtual/virtualDevice.ts index ad5b7218..f7b4178a 100644 --- a/src/device/protocol/virtual/virtualDevice.ts +++ b/src/device/protocol/virtual/virtualDevice.ts @@ -6,6 +6,7 @@ import VirtualDeviceLogic, { AnyVirtualDeviceLogic, ExtractAttributes, ExtractCo import EventEmitter from 'events'; import Logger from '../../../logging/Logger.js'; import { DeviceId } from '../../deviceId.js'; +import { normalizeError } from '../../../util/typeUtils.js'; export type AnyVirtualDevice = VirtualDevice; @@ -53,10 +54,10 @@ export default class VirtualDevice< try { await this.deviceLogic.refreshData(this); } catch (e: unknown) { - const error = BaseError.normalize(e); + const error = normalizeError(e); this.state = DeviceState.error; this.errorInfo = { - reason: error.message ?? 'Unknown error', + reason: error.message, occurredAt: new Date(), }; @@ -77,7 +78,7 @@ export default class VirtualDevice< const attribute = this.attributes[attributeName]; - if (undefined === attribute || null === attribute) { + if (undefined === attribute) { reject(new Error( `Attribute named "${attributeName}" does not exist for device with id "${this.deviceId}"`, )); diff --git a/src/device/protocol/virtual/virtualDeviceProvider.ts b/src/device/protocol/virtual/virtualDeviceProvider.ts index e92cf31b..76230a27 100644 --- a/src/device/protocol/virtual/virtualDeviceProvider.ts +++ b/src/device/protocol/virtual/virtualDeviceProvider.ts @@ -49,8 +49,10 @@ export default class VirtualDeviceProvider extends DeviceProvider { + protected override doStop(): Promise { this.settingsManager.off(SettingsEventType.changed, this.settingsChangedListener); + + return Promise.resolve(); } protected override canHandleDeviceDetectionInfo(deviceDetectionInfo: DeviceDetectionInfo): deviceDetectionInfo is VirtualDeviceDetectionInfo { diff --git a/src/device/protocol/zc95/zc95Device.ts b/src/device/protocol/zc95/zc95Device.ts index 68aa1d1e..61892de5 100644 --- a/src/device/protocol/zc95/zc95Device.ts +++ b/src/device/protocol/zc95/zc95Device.ts @@ -38,7 +38,7 @@ type Zc95DevicePowerChannelAttributes = Record>; +type Zc95DevicePatternAttributes = Partial>>; export type Zc95DeviceAttributes = Partial & Zc95DevicePatternAttributes> & Required; @@ -206,12 +206,9 @@ export default class Zc95Device extends PeripheralDevice { - return attribute !== undefined - && attribute.name.startsWith(Zc95Device.powerChannelAttributePrefix) + return attribute.name.startsWith(Zc95Device.powerChannelAttributePrefix) && ['1', '2', '3', '4'].includes(attribute.name.slice(Zc95Device.powerChannelAttributePrefix.length)); } private isPatternStartedAttribute(attribute: AnyZc95DeviceAttribute): attribute is Zc95DeviceAttributes['patternStarted'] & { name: 'patternStarted' } { - return attribute?.name === 'patternStarted'; + return attribute.name === 'patternStarted'; } private isActivePatternAttribute(attribute: AnyZc95DeviceAttribute): attribute is Zc95DeviceAttributes['activePattern'] & { name: 'activePattern' } { - return attribute?.name === 'activePattern'; + return attribute.name === 'activePattern'; } private isPatternDetailAttribute(attribute: AnyZc95DeviceAttribute): attribute is DeviceAttributeOf { - return attribute !== undefined - && attribute.name.startsWith(Zc95Device.patternAttributePrefix) + return attribute.name.startsWith(Zc95Device.patternAttributePrefix) && !isNaN(parseInt(attribute.name.slice(Zc95Device.patternAttributePrefix.length), 10)); } diff --git a/src/device/protocol/zc95/zc95Protocol.ts b/src/device/protocol/zc95/zc95Protocol.ts index ed8a1af3..cccb9f0f 100644 --- a/src/device/protocol/zc95/zc95Protocol.ts +++ b/src/device/protocol/zc95/zc95Protocol.ts @@ -1,5 +1,18 @@ import DeviceProtocol, { DecodeResult, InferMessage, InferResponse, MessageWithResponse } from '../deviceProtocol.js'; -import BaseError from 'modern-errors'; +import { Type, Static } from '@sinclair/typebox'; +import JsonSchemaValidatorFactory from '../../../schemaValidation/JsonSchemaValidatorFactory.js'; +import JsonSchemaValidator from '../../../schemaValidation/JsonSchemaValidator.js'; +import { parseAndValidateJson } from '../../../util/json.js'; +import { normalizeError } from '../../../util/typeUtils.js'; + +const MsgResponseSchema = Type.Object({ + Type: Type.String(), + MsgId: Type.Number(), + Result: Type.Union([Type.Literal('OK'), Type.Literal('ERROR')]), + Error: Type.Optional(Type.String()), +}); + +export type MsgResponse = Static; type ResponseToKey = R extends { Type: infer T } ? T : never; @@ -8,13 +21,6 @@ export type Msg = { MsgId: number; }; -export type MsgResponse = { - Type: string; - MsgId: number; - Result: 'OK' | 'ERROR'; - Error?: string; -}; - type ResponseIdentifier = { msgId: number; type: ResponseToKey; @@ -32,23 +38,28 @@ export default class Zc95Protocol implements DeviceProtocol public static readonly ETX = 0x03; public static readonly EOT = 0x04; + private readonly msgResponseValidator: JsonSchemaValidator; + + public constructor(jsonSchemaValidatorFactory: JsonSchemaValidatorFactory) { + this.msgResponseValidator = jsonSchemaValidatorFactory.create(MsgResponseSchema); + } + public encode(message: InferMessage): Buffer { return Buffer.from(JSON.stringify(message), 'utf-8'); } public decode(data: Buffer): DecodeResult> { try { - const jsonResponse = JSON.parse(data.toString('utf-8')); + const jsonResponse = parseAndValidateJson(data.toString('utf-8'), this.msgResponseValidator); return { message: jsonResponse, }; } catch (e: unknown) { - const error = BaseError.normalize(e); return { error: { type: 'invalid_frame', - reason: `Could not parse JSON: ${error.message}`, + reason: normalizeError(e).message, }, }; } diff --git a/src/device/protocol/zc95/zc95SerialDeviceProvider.ts b/src/device/protocol/zc95/zc95SerialDeviceProvider.ts index 676cbd26..0e551e8d 100644 --- a/src/device/protocol/zc95/zc95SerialDeviceProvider.ts +++ b/src/device/protocol/zc95/zc95SerialDeviceProvider.ts @@ -1,5 +1,4 @@ import { SerialPortStream } from '@serialport/stream'; -import { BindingInterface } from '@serialport/bindings-interface'; import Logger from '../../../logging/Logger.js'; import SerialDeviceProvider, { SerialDeviceProviderPortOpenOptions } from '../../provider/serialDeviceProvider.js'; import Zc95DeviceFactory from './zc95DeviceFactory.js'; @@ -13,6 +12,7 @@ import Zc95MessageFactory from './zc95MessageFactory.js'; import SerialDeviceTransportFactory from '../../transport/serialDeviceTransportFactory.js'; import DeviceManager from '../../deviceManager.js'; import SerialPortObserver, { SerialDeviceDetectionInfo } from '../../transport/serialPortObserver.js'; +import JsonSchemaValidatorFactory from '../../../schemaValidation/JsonSchemaValidatorFactory.js'; export default class Zc95SerialDeviceProvider extends SerialDeviceProvider { @@ -22,21 +22,25 @@ export default class Zc95SerialDeviceProvider extends SerialDeviceProvider): Promise { + protected async connectSerialDevice(deviceDetectionInfo: SerialDeviceDetectionInfo, port: SerialPortStream): Promise { const serialLogger = this.logger.child({ name: Zc95Device.name }); const parser = port.pipe(new FrameParser({ stx: Zc95Protocol.STX, etx: Zc95Protocol.ETX })); @@ -44,7 +48,7 @@ export default class Zc95SerialDeviceProvider extends SerialDeviceProvider, close = false): Promise { + private async reset(port: SerialPortStream, close = false): Promise { return new Promise((resolve, reject) => { port.write(Buffer.from([Zc95Protocol.EOT]), (writeErr: Error | null | undefined) => { if (null != writeErr) { diff --git a/src/device/provider/deviceProviderManager.ts b/src/device/provider/deviceProviderManager.ts index 77a28c24..61e7e87e 100644 --- a/src/device/provider/deviceProviderManager.ts +++ b/src/device/provider/deviceProviderManager.ts @@ -126,10 +126,10 @@ export default class DeviceProviderManager const errors = results .filter((result): result is PromiseRejectedResult => 'rejected' === result.status) - .map(result => result.reason); + .map((result): unknown => result.reason); for (const error of errors) { - this.logger.error('Failed to stop device provider', error); + logError(this.logger, 'Failed to stop device provider', error); } if (errors.length > 0) { diff --git a/src/device/provider/serialDeviceProvider.ts b/src/device/provider/serialDeviceProvider.ts index 312a46d4..1354eae1 100644 --- a/src/device/provider/serialDeviceProvider.ts +++ b/src/device/provider/serialDeviceProvider.ts @@ -1,6 +1,6 @@ import DeviceProvider from './deviceProvider.js'; import Logger from '../../logging/Logger.js'; -import { BindingInterface, PortInfo } from '@serialport/bindings-interface'; +import { PortInfo } from '@serialport/bindings-interface'; import { SerialPortOpenOptions } from 'serialport'; import { SerialPortStream } from '@serialport/stream'; import SerialPortFactory from '../../factory/serialPortFactory.js'; @@ -86,11 +86,11 @@ export default abstract class SerialDeviceProvider, portInfo: PortInfo): Promise { + protected preparePort(port: SerialPortStream, portInfo: PortInfo): Promise { return Promise.resolve(); } - protected abstract connectSerialDevice(deviceDetectionInfo: SerialDeviceDetectionInfo, port: SerialPortStream): Promise; + protected abstract connectSerialDevice(deviceDetectionInfo: SerialDeviceDetectionInfo, port: SerialPortStream): Promise; protected abstract getSerialDeviceProviderPortOpenOptions(portInfo: PortInfo): SerialDeviceProviderPortOpenOptions; } diff --git a/src/device/transport/bleDeviceTransport.ts b/src/device/transport/bleDeviceTransport.ts index f2246c9f..5bffa19a 100644 --- a/src/device/transport/bleDeviceTransport.ts +++ b/src/device/transport/bleDeviceTransport.ts @@ -14,7 +14,7 @@ export default class BleUartDeviceTransport implements DeviceBidirectionalTransp private isConnected = false; private isSubscribing = false; - private onCloseSubscribers: (() => Promise)[] = []; + private onCloseSubscribers: (() => void | Promise)[] = []; private onReceiveSubscribers: ((data: Buffer) => void)[] = []; private onConnectedSubscribers: (() => void)[] = []; @@ -36,8 +36,8 @@ export default class BleUartDeviceTransport implements DeviceBidirectionalTransp this.uartRxCharacteristicUuid = uartRxCharacteristicUuid; this.uartTxCharacteristicUuid = uartTxCharacteristicUuid; - this.connectHandler = asyncHandler(async (err: Error) => { - if (null !== err) { + this.connectHandler = asyncHandler(async (err: unknown) => { + if (undefined !== err && null !== err) { return; } await this.subscribe(); @@ -132,7 +132,7 @@ export default class BleUartDeviceTransport implements DeviceBidirectionalTransp this.tx?.on('data', dataProcessor); } - public onClose(callback: () => Promise): void { + public onClose(callback: () => void | Promise): void { this.onCloseSubscribers.push(callback); } diff --git a/src/device/transport/bleObserver.ts b/src/device/transport/bleObserver.ts index 62dfe99c..4acb20ba 100644 --- a/src/device/transport/bleObserver.ts +++ b/src/device/transport/bleObserver.ts @@ -35,7 +35,7 @@ export default class BleObserver extends SharedObserver this.deviceManager = deviceManager; } - protected async onFirstStart(): Promise + protected onFirstStart(): Promise { this.stopRequested = false; @@ -47,6 +47,8 @@ export default class BleObserver extends SharedObserver // Waiting for the adapter to power on can take an arbitrarily long time (or never happen // at all, e.g. no BLE hardware present), so it must not block start()/stop() void this.startScanningOncePoweredOn(); + + return Promise.resolve(); } protected async onLastStop(): Promise @@ -114,16 +116,16 @@ export default class BleObserver extends SharedObserver } private async waitForPoweredOnUnlessStopped(): Promise { - let stopped = false; + const state = { stopped: false }; const stopSignal = new Promise(resolve => { this.cancelPowerOnWait = (): void => { - stopped = true; + state.stopped = true; resolve(); }; }); - while (!stopped) { + while (!state.stopped) { const outcome = await Promise.race([ noble.waitForPoweredOnAsync(BleObserver.POWER_ON_WAIT_CHUNK_MS) .then(() => ({ type: 'poweredOn' as const })) @@ -141,6 +143,6 @@ export default class BleObserver extends SharedObserver this.cancelPowerOnWait = undefined; - return stopped; + return state.stopped; } } diff --git a/src/device/transport/deviceReadableTransport.ts b/src/device/transport/deviceReadableTransport.ts index 462aff9f..bfeffafc 100644 --- a/src/device/transport/deviceReadableTransport.ts +++ b/src/device/transport/deviceReadableTransport.ts @@ -9,7 +9,7 @@ type DeviceReadableTransport = { * Called when the transport is closed (for example when a serial device is unplugged) * @param callback */ - onClose(callback: () => Promise): void; + onClose(callback: () => void | Promise): void; /** * Returns whether the transport is currently open/active or not diff --git a/src/device/transport/deviceWritableTransport.ts b/src/device/transport/deviceWritableTransport.ts index 4eb09294..dbe92257 100644 --- a/src/device/transport/deviceWritableTransport.ts +++ b/src/device/transport/deviceWritableTransport.ts @@ -9,7 +9,7 @@ type DeviceWritableTransport = { * Called when the transport is closed (for example when a serial device is unplugged) * @param callback */ - onClose(callback: () => Promise): void; + onClose(callback: () => void | Promise): void; /** * Returns whether the transport is currently open/active or not diff --git a/src/device/transport/serialDeviceTransport.ts b/src/device/transport/serialDeviceTransport.ts index f01df415..00b156d6 100644 --- a/src/device/transport/serialDeviceTransport.ts +++ b/src/device/transport/serialDeviceTransport.ts @@ -25,7 +25,7 @@ export default class SerialDeviceTransport implements DeviceBidirectionalTranspo this.serialPort.onData(dataProcessor); } - public onClose(callback: () => Promise): void { + public onClose(callback: () => void | Promise): void { this.serialPort.onClose(callback); } @@ -54,7 +54,7 @@ export default class SerialDeviceTransport implements DeviceBidirectionalTranspo } private addFrameBoundaries(frame: Buffer): Buffer { - const frameWithBoundaries: Buffer[] = []; + const frameWithBoundaries: Buffer[] = []; if (undefined !== this.frameStartBytes) { frameWithBoundaries.push(this.frameStartBytes); diff --git a/src/device/transport/serialPortObserver.ts b/src/device/transport/serialPortObserver.ts index 3c26d9be..d6de2931 100644 --- a/src/device/transport/serialPortObserver.ts +++ b/src/device/transport/serialPortObserver.ts @@ -69,13 +69,15 @@ export default class SerialPortObserver extends SharedObserver * ports, so a device already sitting unclaimed here (e.g. because no provider wanted it yet) * would otherwise never be offered to this provider. */ - protected override async onSubsequentStart(): Promise + protected override onSubsequentStart(): Promise { // announceDetectedDevice() itself is a no-op for a device that's already connected, so // there's no need to filter those out here first. for (const deviceInfo of this.managedDevices.values()) { this.deviceManager.announceDetectedDevice(deviceInfo); } + + return Promise.resolve(); } public async discoverSerialDevices(cancellationToken?: CancellationToken): Promise @@ -102,9 +104,7 @@ export default class SerialPortObserver extends SharedObserver } // If the serial number is not defined, create a "unique" one based on vendorId and productId - if (undefined === portInfo.serialNumber) { - portInfo.serialNumber = `serial-${portInfo.vendorId}-${portInfo.productId}-${portInfo.locationId}`; - } + portInfo.serialNumber ??= `serial-${portInfo.vendorId}-${portInfo.productId}-${portInfo.locationId}`; foundDevices.set(portInfo.serialNumber, null); diff --git a/src/factory/serialPortFactory.ts b/src/factory/serialPortFactory.ts index c4aa47ac..919ac263 100644 --- a/src/factory/serialPortFactory.ts +++ b/src/factory/serialPortFactory.ts @@ -1,11 +1,10 @@ import { SerialPort, SerialPortOpenOptions } from 'serialport'; import { SerialPortStream } from '@serialport/stream'; import { AutoDetectTypes } from '@serialport/bindings-cpp'; -import { BindingInterface } from '@serialport/bindings-interface'; export default class SerialPortFactory { - public create(options: SerialPortOpenOptions): SerialPortStream { + public create(options: SerialPortOpenOptions): SerialPortStream { return new SerialPort(options); } } diff --git a/src/health/healthMetricsCollector.ts b/src/health/healthMetricsCollector.ts index 78f10d2f..b307f05c 100644 --- a/src/health/healthMetricsCollector.ts +++ b/src/health/healthMetricsCollector.ts @@ -85,12 +85,12 @@ export default class HealthMetricsCollector }, system: { cpu: { - usage: true === cpuUsage.success ? cpuUsage.data : null, - average: true === cpuLoadAvg.success ? cpuLoadAvg.data.load1 : null, - cores: true === cpuInfo.success ? cpuInfo.data.cores : null, - model: true === cpuInfo.success ? cpuInfo.data.model : null, + usage: cpuUsage.success ? cpuUsage.data : null, + average: cpuLoadAvg.success ? cpuLoadAvg.data.load1 : null, + cores: cpuInfo.success ? cpuInfo.data.cores : null, + model: cpuInfo.success ? cpuInfo.data.model : null, }, - memory: true === memInfo.success + memory: memInfo.success ? { totalMemMb: memInfo.data.total.toMB(), usedMemMb: memInfo.data.used.toMB(), @@ -106,15 +106,15 @@ export default class HealthMetricsCollector platform: os.platform(), }, network: { - netstat: true === networkStats.success ? networkStats.data : null, + netstat: networkStats.success ? networkStats.data : null, }, - ip: true === networkInterfaces.success + ip: networkInterfaces.success ? (networkInterfaces.data - .find(i => i.internal === false && i.type !== 'loopback' && i.addresses.some(a => a.family === 'IPv4' && a.internal === false)) - ?.addresses.find(a => a.family === 'IPv4' && a.internal === false)?.address ?? null) + .find(i => !i.internal && i.type !== 'loopback' && i.addresses.some(a => a.family === 'IPv4' && !a.internal)) + ?.addresses.find(a => a.family === 'IPv4' && !a.internal)?.address ?? null) : null, hostname: os.hostname(), - uptime: true === sysUptime.success ? Math.floor(sysUptime.data.uptime / 1000) : null, + uptime: sysUptime.success ? Math.floor(sysUptime.data.uptime / 1000) : null, }, }; diff --git a/src/logging/Logger.ts b/src/logging/Logger.ts index f2284b96..48010c1e 100644 --- a/src/logging/Logger.ts +++ b/src/logging/Logger.ts @@ -1,8 +1,6 @@ export type ChildLoggerBindings = { name?: string }; export type ChildLoggerOptions = { level?: string }; -export type ErrorContext = Error | ({ error: Error } & Record); - type Logger = { child(bindings?: ChildLoggerBindings, options?: ChildLoggerOptions): Logger; @@ -10,7 +8,7 @@ type Logger = { debug(msg: string, context?: unknown): void; info(msg: string, context?: unknown): void; warn(msg: string, context?: unknown): void; - error(msg: string, context?: ErrorContext | unknown): void; + error(msg: string, context?: unknown): void; fatal(msg: string, context?: unknown): void; }; diff --git a/src/logging/PinoLogger.ts b/src/logging/PinoLogger.ts index 5caf06b3..a3c921fe 100644 --- a/src/logging/PinoLogger.ts +++ b/src/logging/PinoLogger.ts @@ -1,4 +1,4 @@ -import Logger, { ChildLoggerBindings, ChildLoggerOptions, ErrorContext } from './Logger.js'; +import Logger, { ChildLoggerBindings, ChildLoggerOptions } from './Logger.js'; import pino from 'pino/pino.js'; export default class PinoLogger implements Logger @@ -12,7 +12,7 @@ export default class PinoLogger implements Logger public child(bindings?: ChildLoggerBindings, options?: ChildLoggerOptions): Logger { return new PinoLogger(this.pino.child( bindings ?? {}, - undefined === options ? undefined : options, + options ?? undefined, )); } @@ -32,7 +32,7 @@ export default class PinoLogger implements Logger this.pino.warn(context, msg); } - public error(msg: string, context?: ErrorContext | unknown): void { + public error(msg: string, context?: unknown): void { this.pino.error(context, msg); } diff --git a/src/schemaValidation/JsonSchemaValidatorFactory.ts b/src/schemaValidation/JsonSchemaValidatorFactory.ts index 2f17e907..6bb9a74e 100644 --- a/src/schemaValidation/JsonSchemaValidatorFactory.ts +++ b/src/schemaValidation/JsonSchemaValidatorFactory.ts @@ -1,5 +1,4 @@ import { Ajv } from 'ajv'; -import fs from 'fs'; import JsonSchemaValidator from './JsonSchemaValidator.js'; import { TSchema } from '@sinclair/typebox'; @@ -14,9 +13,4 @@ export default class JsonSchemaValidatorFactory public create(schema: T): JsonSchemaValidator { return new JsonSchemaValidator(this.ajv, schema); } - - public createFromFile(schemaFilePath: string): JsonSchemaValidator { - const schemaData: T = JSON.parse(fs.readFileSync(schemaFilePath, 'utf-8')); - return new JsonSchemaValidator(this.ajv, schemaData); - } } diff --git a/src/serial/synchronousSerialPort.ts b/src/serial/synchronousSerialPort.ts index d6e456f9..97468bb8 100644 --- a/src/serial/synchronousSerialPort.ts +++ b/src/serial/synchronousSerialPort.ts @@ -1,17 +1,17 @@ import { Readable } from 'stream'; import { SerialPortStream } from '@serialport/stream'; import { cancellationTokenReasons, SequentialTaskQueue, TaskOptions } from '@timesplinter/sequential-task-queue'; -import { BindingInterface, PortInfo } from '@serialport/bindings-interface'; +import { PortInfo } from '@serialport/bindings-interface'; import Logger from '../logging/Logger.js'; import { logError } from '../util/error.js'; -type CloseHandler = () => Promise; +type CloseHandler = () => void | Promise; export default class SynchronousSerialPort { private reader: Readable; - private writer: SerialPortStream; + private writer: SerialPortStream; private readonly portInfo: PortInfo; @@ -29,7 +29,7 @@ export default class SynchronousSerialPort this.close().catch((err: unknown) => logError(this.logger, 'Error closing serial port after stream close', err)); }; - public constructor(portInfo: PortInfo, reader: Readable, writer: SerialPortStream, logger: Logger) { + public constructor(portInfo: PortInfo, reader: Readable, writer: SerialPortStream, logger: Logger) { this.portInfo = portInfo; this.reader = reader; this.writer = writer; @@ -111,9 +111,7 @@ export default class SynchronousSerialPort }; const dataHandler = (receivedData: Buffer): void => { - if (undefined !== removeListeners) { - removeListeners(); - } + removeListeners(); resolve(receivedData); }; diff --git a/src/serialization/classToPlainSerializer.ts b/src/serialization/classToPlainSerializer.ts index 9e0895e8..19ccbed4 100644 --- a/src/serialization/classToPlainSerializer.ts +++ b/src/serialization/classToPlainSerializer.ts @@ -9,12 +9,13 @@ export default class ClassToPlainSerializer this.options = options; } + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-parameters -- TOut is caller-specified as a type witness for the result (e.g. serializer.transform(...)), not inferred from another param public transform>(object: object, typeOptions?: TypeOptions | null): TOut; public transform(object: object, typeOptions: TypeOptions | null = null): Record { const result = instanceToPlain(object, this.options); - if (typeOptions !== null && typeOptions.discriminator !== undefined) { + if (typeOptions?.discriminator !== undefined) { const discriminatorValue = typeOptions.discriminator.subTypes.find(obj => obj.value === object.constructor); if (undefined === discriminatorValue) { diff --git a/src/serialization/plainToClassSerializer.ts b/src/serialization/plainToClassSerializer.ts index f5e1b0b8..ac7d6f1e 100644 --- a/src/serialization/plainToClassSerializer.ts +++ b/src/serialization/plainToClassSerializer.ts @@ -16,7 +16,7 @@ export default class PlainToClassSerializer this.options = options; } - public transform(cls: ClassConstructor, plain: V, schema?: TSchema): T + public transform(cls: ClassConstructor, plain: unknown, schema?: TSchema): T { if (undefined === schema) { return plainToInstance(cls, plain, this.options); diff --git a/src/serviceProvider/automationServiceProvider.ts b/src/serviceProvider/automationServiceProvider.ts index 11cc134e..f7de3c48 100644 --- a/src/serviceProvider/automationServiceProvider.ts +++ b/src/serviceProvider/automationServiceProvider.ts @@ -23,7 +23,7 @@ export default class AutomationServiceProvider implements ServiceProvider { const logPath = `${this.dataPath}/automation-logs`; - if (false === fs.existsSync(logPath)) { + if (!fs.existsSync(logPath)) { fs.mkdirSync(logPath, { recursive: true }); } diff --git a/src/serviceProvider/deviceServiceProvider.ts b/src/serviceProvider/deviceServiceProvider.ts index d9a9234a..394eea63 100644 --- a/src/serviceProvider/deviceServiceProvider.ts +++ b/src/serviceProvider/deviceServiceProvider.ts @@ -213,6 +213,7 @@ export default class DeviceServiceProvider implements ServiceProvider { const scriptsPath = `${this.dataPath}/automation-scripts`; - if (false === fs.existsSync(scriptsPath)) { + if (!fs.existsSync(scriptsPath)) { fs.mkdirSync(scriptsPath, { recursive: true }); } diff --git a/src/serviceProvider/settingsServiceProvider.ts b/src/serviceProvider/settingsServiceProvider.ts index 5d19437e..320d2901 100644 --- a/src/serviceProvider/settingsServiceProvider.ts +++ b/src/serviceProvider/settingsServiceProvider.ts @@ -16,7 +16,7 @@ export default class SettingsServiceProvider implements ServiceProvider { const dataPath = this.dataPath ?? `${os.homedir()}/.slvctrlplus`; - if (false === fs.existsSync(dataPath)) { + if (!fs.existsSync(dataPath)) { fs.mkdirSync(dataPath, { recursive: true }); } diff --git a/src/settings/settingsManager.ts b/src/settings/settingsManager.ts index baee2ee3..4f8896af 100644 --- a/src/settings/settingsManager.ts +++ b/src/settings/settingsManager.ts @@ -9,7 +9,6 @@ import SlvCtrlPlusSerialDeviceProvider from '../device/protocol/slvCtrlPlus/slvC import Logger from '../logging/Logger.js'; import EventEmitter from 'events'; import SettingsEventType from './settingsEventType.js'; -import { JsonObject } from '../types.js'; import { logError } from '../util/error.js'; type SettingsEvents = { @@ -61,7 +60,8 @@ export default class SettingsManager const fileContent = fs.readFileSync(this.settingsFilePath, 'utf8'); try { - const plainJsonSettings: JsonObject = JSON.parse(fileContent); + // TODO: centralize json parsing and schema validation. Happens twice in this class + const plainJsonSettings: unknown = JSON.parse(fileContent); this.settings = this.transformPlainToSettings(plainJsonSettings); } catch (e: unknown) { logError(this.logger, 'Settings are not in a valid format', e); @@ -172,7 +172,7 @@ export default class SettingsManager return; } - let plainJsonSettings: JsonObject; + let plainJsonSettings: unknown; try { plainJsonSettings = JSON.parse(content); @@ -196,7 +196,7 @@ export default class SettingsManager this.logger.info(`Settings reloaded after external change to '${this.settingsFilePath}'`); } - private transformPlainToSettings(plainJsonSettings: JsonObject): Settings { + private transformPlainToSettings(plainJsonSettings: unknown): Settings { return this.plainToClassSerializer.transform(Settings, plainJsonSettings, SettingsSchema); } diff --git a/src/util/expressUtils.ts b/src/util/expressUtils.ts index 7f95e9f5..0a6b5ae1 100644 --- a/src/util/expressUtils.ts +++ b/src/util/expressUtils.ts @@ -7,9 +7,9 @@ export type ControllerKey = { [K in keyof ServiceMap]: K extends `controller.${string}` ? K : never }[keyof ServiceMap]; -export const executeController = ( +export const executeController = ( container: Container, - controllerName: K, + controllerName: ControllerKey, ): (req: Request, res: Response) => void | Promise => { return (req: Request, res: Response) => { const controller: ControllerInterface = container.get(controllerName); diff --git a/src/util/json.ts b/src/util/json.ts new file mode 100644 index 00000000..f861bce8 --- /dev/null +++ b/src/util/json.ts @@ -0,0 +1,25 @@ +import { Static } from '@sinclair/typebox'; +import JsonSchemaValidator from '../schemaValidation/JsonSchemaValidator.js'; +import type { TSchema } from '@sinclair/typebox'; +import SchemaValidationError from '../schemaValidation/schemaValidationError.js'; +import { normalizeError } from './typeUtils.js'; + +export const parseAndValidateJson = (data: string, jsonSchemaValidator: JsonSchemaValidator): Static => { + let json: unknown; + + try { + json = JSON.parse(data); + } catch (e: unknown) { + throw new Error(`Could not parse JSON: ${normalizeError(e).message}`, { cause: e }); + } + + if (!jsonSchemaValidator.validate(json)) { + const validationError = new SchemaValidationError( + jsonSchemaValidator.getValidationErrorsAsText(), + jsonSchemaValidator.getValidationErrors(), + ); + throw new Error(`Invalid JSON shape: ${normalizeError(validationError).message}`, { cause: validationError }); + } + + return json; +}; diff --git a/src/util/typeUtils.ts b/src/util/typeUtils.ts new file mode 100644 index 00000000..fdfbd62d --- /dev/null +++ b/src/util/typeUtils.ts @@ -0,0 +1,11 @@ +import BaseError from 'modern-errors'; + +type TupleOfExactly = + Acc['length'] extends N ? Acc : TupleOfExactly; + +export const hasExactLength = ( + arr: readonly T[], + length: N, +): arr is TupleOfExactly => arr.length === length; + +export const normalizeError = (error: unknown): Error => error instanceof Error ? error : BaseError.normalize(error); diff --git a/tests/integration/automationScripts.spec.ts b/tests/integration/automationScripts.spec.ts index d3c2c456..c6708579 100644 --- a/tests/integration/automationScripts.spec.ts +++ b/tests/integration/automationScripts.spec.ts @@ -198,7 +198,7 @@ describe('Automation scripts', () => { `); const device = app.container.get('device.manager').getConnectedDevices()[0]; - await device.close(); + await device?.close(); const logs = await logsPromise; expect(logs).toContain(TEST_DEVICE_ID); diff --git a/tests/integration/deviceEvents.spec.ts b/tests/integration/deviceEvents.spec.ts index acb18713..8a46899d 100644 --- a/tests/integration/deviceEvents.spec.ts +++ b/tests/integration/deviceEvents.spec.ts @@ -37,7 +37,7 @@ describe('Device events', () => { const devices = app.container.get('device.manager').getConnectedDevices(); expect(devices).toHaveLength(1); - expect(devices[0].getDeviceId).toBe(TEST_DEVICE_ID); + expect(devices[0]?.getDeviceId).toBe(TEST_DEVICE_ID); }); it('virtual device gets refreshed', async () => { @@ -56,7 +56,7 @@ describe('Device events', () => { }, 1000); const listener = async () => { - const value = (await device.getAttribute('value'))?.value; + const value = (await device?.getAttribute('value'))?.value; if (undefined === observedValue) { observedValue = value; @@ -248,7 +248,7 @@ describe('Device events', () => { }); }); - await device.close(); + await device?.close(); await disconnected; }); }); diff --git a/tests/integration/helpers/appHelper.ts b/tests/integration/helpers/appHelper.ts index 6e9825b5..aa5f0d45 100644 --- a/tests/integration/helpers/appHelper.ts +++ b/tests/integration/helpers/appHelper.ts @@ -149,7 +149,7 @@ export function waitForNextWsEvent( timeoutMs = 5000, predicate?: (params: Parameters) => boolean, ): Promise> { - const matchingCalls = () => wsEmitSpy.mock.calls + const matchingCalls = () => wsEmitSpy.mock.calls .filter((call): call is Extract]> => call[0] === event) .map(([, ...params]) => params as Parameters) .filter(params => predicate === undefined || predicate(params)); @@ -158,8 +158,10 @@ export function waitForNextWsEvent( const deadline = Date.now() + timeoutMs; const poll = () => { const calls = matchingCalls(); - if (calls.length > countBefore) { - resolve(calls[calls.length - 1]); + const lastCall = calls.length > countBefore ? calls[calls.length - 1] : undefined; + + if (lastCall !== undefined) { + resolve(lastCall); } else if (Date.now() >= deadline) { reject(new Error(`Timed out waiting for WS event '${event}' (>${timeoutMs}ms)`)); } else { diff --git a/tests/integration/helpers/estim2bDeviceSimulator.ts b/tests/integration/helpers/estim2bDeviceSimulator.ts index a4613cd6..176e9fab 100644 --- a/tests/integration/helpers/estim2bDeviceSimulator.ts +++ b/tests/integration/helpers/estim2bDeviceSimulator.ts @@ -44,7 +44,9 @@ export class Estim2bDeviceSimulator { this.handleCommand(command); setImmediate(() => { - bindingPort.emitData(Buffer.from(this.buildStatusResponse() + '\n', 'utf-8')); + if (bindingPort.isOpen) { + bindingPort.emitData(Buffer.from(this.buildStatusResponse() + '\n', 'utf-8')); + } }); }; } diff --git a/tests/integration/helpers/zc95DeviceSimulator.ts b/tests/integration/helpers/zc95DeviceSimulator.ts index b4cb8326..b3062be4 100644 --- a/tests/integration/helpers/zc95DeviceSimulator.ts +++ b/tests/integration/helpers/zc95DeviceSimulator.ts @@ -153,10 +153,10 @@ export class Zc95DeviceSimulator { Type: 'PatternDetail', MsgId: msg.MsgId, Result: 'OK', - Name: pattern.name, - Id: pattern.id, + Name: pattern?.name, + Id: pattern?.id, ButtonA: '', - MenuItems: pattern.menuItems ?? [], + MenuItems: pattern?.menuItems ?? [], }; } diff --git a/tests/unit/automation/scriptRuntime.spec.ts b/tests/unit/automation/scriptRuntime.spec.ts index 80b5bc5a..51e740b3 100644 --- a/tests/unit/automation/scriptRuntime.spec.ts +++ b/tests/unit/automation/scriptRuntime.spec.ts @@ -12,6 +12,7 @@ import DeviceRepositoryInterface from '../../../src/repository/deviceRepositoryI import StrDeviceAttribute from '../../../src/device/attribute/strDeviceAttribute.js'; import Logger from '../../../src/logging/Logger.js'; import { DeviceId } from '../../../src/device/deviceId.js'; +import assert from 'assert'; // --------------------------------------------------------------------------- // Stub device with a single 'label' string attribute @@ -194,8 +195,13 @@ describe('ScriptRuntime (isolated-vm)', () => { `); const logs = await dispatchAndCollect(eventEmitter, runtime, deviceA, TEST_END_MARKER); - expect(logs[0]).not.toContain('[object Object]'); - expect(JSON.parse(logs[0])).toStrictEqual({ foo: 'bar', nested: { baz: 42 } }); + expect(logs).toHaveLength(2); + + const [log] = logs; + assert(log !== undefined, 'Expected at least one log entry'); + + expect(log).not.toContain('[object Object]'); + expect(JSON.parse(log)).toStrictEqual({ foo: 'bar', nested: { baz: 42 } }); }); it('console.log formats array and mixed arguments readably', async () => { diff --git a/tests/unit/device/protocol/slvCtrlPlus/slvCtrlProtocolLegacy.spec.ts b/tests/unit/device/protocol/slvCtrlPlus/slvCtrlProtocolLegacy.spec.ts index abfd08ee..b59d1df4 100644 --- a/tests/unit/device/protocol/slvCtrlPlus/slvCtrlProtocolLegacy.spec.ts +++ b/tests/unit/device/protocol/slvCtrlPlus/slvCtrlProtocolLegacy.spec.ts @@ -66,36 +66,36 @@ describe('slvCtrlProtocolLegacy', () => { expect(Object.keys(result).length).toBe(7); expect(result.connected).toBeInstanceOf(BoolDeviceAttribute); - expect(result.connected.name).toBe('connected'); - expect(result.connected.modifier).toBe(DeviceAttributeModifier.readOnly); + expect(result.connected?.name).toBe('connected'); + expect(result.connected?.modifier).toBe(DeviceAttributeModifier.readOnly); expect(result.adc).toBeInstanceOf(BoolDeviceAttribute); - expect(result.adc.name).toBe('adc'); - expect(result.adc.modifier).toBe(DeviceAttributeModifier.readWrite); + expect(result.adc?.name).toBe('adc'); + expect(result.adc?.modifier).toBe(DeviceAttributeModifier.readWrite); expect(result.mode).toBeInstanceOf(IntRangeDeviceAttribute); - expect(result.mode.name).toBe('mode'); - expect(result.mode.modifier).toBe(DeviceAttributeModifier.readWrite); + expect(result.mode?.name).toBe('mode'); + expect(result.mode?.modifier).toBe(DeviceAttributeModifier.readWrite); expect((result.mode as IntRangeDeviceAttribute).min).toBe(118); expect((result.mode as IntRangeDeviceAttribute).max).toBe(140); expect(result.levelA).toBeInstanceOf(IntDeviceAttribute); - expect(result.levelA.name).toBe('levelA'); - expect(result.levelA.modifier).toBe(DeviceAttributeModifier.readWrite); + expect(result.levelA?.name).toBe('levelA'); + expect(result.levelA?.modifier).toBe(DeviceAttributeModifier.readWrite); expect(result.levelB).toBeInstanceOf(ListDeviceAttribute); - expect(result.levelB.name).toBe('levelB'); - expect(result.levelB.modifier).toBe(DeviceAttributeModifier.readWrite); + expect(result.levelB?.name).toBe('levelB'); + expect(result.levelB?.modifier).toBe(DeviceAttributeModifier.readWrite); expect((result.levelB as ListDeviceAttribute).values) .toStrictEqual([{ key: 'foo', value: 'foo'}, { key: 'bar', value: 'bar' }, { key: 'baz', value: 'baz'}]); expect(result.levelC).toBeInstanceOf(StrDeviceAttribute); - expect(result.levelC.name).toBe('levelC'); - expect(result.levelC.modifier).toBe(DeviceAttributeModifier.writeOnly); + expect(result.levelC?.name).toBe('levelC'); + expect(result.levelC?.modifier).toBe(DeviceAttributeModifier.writeOnly); expect(result.levelD).toBeInstanceOf(FloatDeviceAttribute); - expect(result.levelD.name).toBe('levelD'); - expect(result.levelD.modifier).toBe(DeviceAttributeModifier.readWrite); + expect(result.levelD?.name).toBe('levelD'); + expect(result.levelD?.modifier).toBe(DeviceAttributeModifier.readWrite); }); it('it parses a successful device attribute response with no attributes', async () => { @@ -148,8 +148,8 @@ describe('slvCtrlProtocolLegacy', () => { expect(Object.keys(result).length).toBe(1); expect(result.bar).toBeInstanceOf(BoolDeviceAttribute); - expect(result.bar.name).toBe('bar'); - expect(result.bar.modifier).toBe(DeviceAttributeModifier.readWrite); + expect(result.bar?.name).toBe('bar'); + expect(result.bar?.modifier).toBe(DeviceAttributeModifier.readWrite); }); it('it parses successful status response', async () => { diff --git a/tests/unit/device/protocol/slvCtrlPlus/slvCtrlProtocolV1.spec.ts b/tests/unit/device/protocol/slvCtrlPlus/slvCtrlProtocolV1.spec.ts index 2b8edd39..521ad7be 100644 --- a/tests/unit/device/protocol/slvCtrlPlus/slvCtrlProtocolV1.spec.ts +++ b/tests/unit/device/protocol/slvCtrlPlus/slvCtrlProtocolV1.spec.ts @@ -63,40 +63,40 @@ describe('slvCtrlProtocolV1', () => { expect(Object.keys(result).length).toBe(8); expect(result.connected).toBeInstanceOf(BoolDeviceAttribute); - expect(result.connected.name).toBe('connected'); - expect(result.connected.modifier).toBe(DeviceAttributeModifier.readOnly); + expect(result.connected?.name).toBe('connected'); + expect(result.connected?.modifier).toBe(DeviceAttributeModifier.readOnly); expect(result.adc).toBeInstanceOf(BoolDeviceAttribute); - expect(result.adc.name).toBe('adc'); - expect(result.adc.modifier).toBe(DeviceAttributeModifier.readWrite); + expect(result.adc?.name).toBe('adc'); + expect(result.adc?.modifier).toBe(DeviceAttributeModifier.readWrite); expect(result.mode).toBeInstanceOf(IntRangeDeviceAttribute); - expect(result.mode.name).toBe('mode'); - expect(result.mode.modifier).toBe(DeviceAttributeModifier.readWrite); + expect(result.mode?.name).toBe('mode'); + expect(result.mode?.modifier).toBe(DeviceAttributeModifier.readWrite); expect((result.mode as IntRangeDeviceAttribute).min).toBe(118); expect((result.mode as IntRangeDeviceAttribute).max).toBe(140); expect(result.levelA).toBeInstanceOf(IntDeviceAttribute); - expect(result.levelA.name).toBe('levelA'); - expect(result.levelA.modifier).toBe(DeviceAttributeModifier.readWrite); + expect(result.levelA?.name).toBe('levelA'); + expect(result.levelA?.modifier).toBe(DeviceAttributeModifier.readWrite); expect(result.levelB).toBeInstanceOf(ListDeviceAttribute); - expect(result.levelB.name).toBe('levelB'); - expect(result.levelB.modifier).toBe(DeviceAttributeModifier.readWrite); + expect(result.levelB?.name).toBe('levelB'); + expect(result.levelB?.modifier).toBe(DeviceAttributeModifier.readWrite); expect((result.levelB as ListDeviceAttribute).values) .toStrictEqual([{ key: 'foo', value: 'foo'}, { key: 'bar', value: 'bar' }, { key: 'baz', value: 'baz'}]); expect(result.levelC).toBeInstanceOf(StrDeviceAttribute); - expect(result.levelC.name).toBe('levelC'); - expect(result.levelC.modifier).toBe(DeviceAttributeModifier.writeOnly); + expect(result.levelC?.name).toBe('levelC'); + expect(result.levelC?.modifier).toBe(DeviceAttributeModifier.writeOnly); expect(result.levelD).toBeInstanceOf(FloatDeviceAttribute); - expect(result.levelD.name).toBe('levelD'); - expect(result.levelD.modifier).toBe(DeviceAttributeModifier.readWrite); + expect(result.levelD?.name).toBe('levelD'); + expect(result.levelD?.modifier).toBe(DeviceAttributeModifier.readWrite); expect(result.levelE).toBeInstanceOf(ListDeviceAttribute); - expect(result.levelE.name).toBe('levelE'); - expect(result.levelE.modifier).toBe(DeviceAttributeModifier.readWrite); + expect(result.levelE?.name).toBe('levelE'); + expect(result.levelE?.modifier).toBe(DeviceAttributeModifier.readWrite); expect((result.levelE as ListDeviceAttribute).values) .toStrictEqual([{ key: 1, value: 1}, { key: 2, value: 2 }]); @@ -170,8 +170,8 @@ describe('slvCtrlProtocolV1', () => { expect(Object.keys(result).length).toBe(1); expect(result.bar).toBeInstanceOf(BoolDeviceAttribute); - expect(result.bar.name).toBe('bar'); - expect(result.bar.modifier).toBe(DeviceAttributeModifier.readWrite); + expect(result.bar?.name).toBe('bar'); + expect(result.bar?.modifier).toBe(DeviceAttributeModifier.readWrite); }); it('it parses successful status response', async () => { diff --git a/tests/unit/device/protocol/zc95/zc95Device.spec.ts b/tests/unit/device/protocol/zc95/zc95Device.spec.ts index 778ee574..241060cb 100644 --- a/tests/unit/device/protocol/zc95/zc95Device.spec.ts +++ b/tests/unit/device/protocol/zc95/zc95Device.spec.ts @@ -19,6 +19,7 @@ import { DeviceAttributeModifier } from '../../../../../src/device/attribute/dev import { Int } from '../../../../../src/util/numbers.js'; import { DeviceId } from '../../../../../src/device/deviceId.js'; import Logger from '../../../../../src/logging/Logger.js'; +import assert from 'assert'; describe('Zc95Device', () => { let mockProtocol: MockProxy; @@ -99,7 +100,11 @@ describe('Zc95Device', () => { } function getOnReceiveCallback(): (data: Buffer) => void { - return mockTransport.onReceive.mock.calls[0][0]; + const call = mockTransport.onReceive.mock.calls[0]; + assert(call !== undefined, 'Expected onReceive to have been registered'); + + return call[0]; + } beforeEach(() => { diff --git a/tests/unit/device/protocol/zc95/zc95DeviceFactory.spec.ts b/tests/unit/device/protocol/zc95/zc95DeviceFactory.spec.ts index d0cb44de..93b5369a 100644 --- a/tests/unit/device/protocol/zc95/zc95DeviceFactory.spec.ts +++ b/tests/unit/device/protocol/zc95/zc95DeviceFactory.spec.ts @@ -16,6 +16,7 @@ import Zc95MessageFactory, { } from '../../../../../src/device/protocol/zc95/zc95MessageFactory.js'; import { MsgAndResponseIdentifier } from '../../../../../src/device/protocol/zc95/zc95Protocol.js'; import { DeviceId, DetectionId } from '../../../../../src/device/deviceId.js'; +import assert from 'assert'; describe('Zc95DeviceFactory', () => { let knownDeviceRegistry: MockProxy; @@ -111,7 +112,11 @@ describe('Zc95DeviceFactory', () => { expect(knownDeviceRegistry.resolve).toHaveBeenCalledWith(expectedDeviceId, 'zc95', provider); expect(knownDeviceRegistry.persist).toHaveBeenCalledTimes(1); - const persisted = knownDeviceRegistry.persist.mock.calls[0][0]; + const call = knownDeviceRegistry.persist.mock.calls[0]; + assert(call !== undefined, 'Expected persist to have been called'); + + const persisted = call[0]; + expect(persisted.id).toStrictEqual(expectedDeviceId); expect(persisted.type).toStrictEqual('zc95'); expect(persisted.source).toStrictEqual(provider); diff --git a/tests/unit/device/protocol/zc95/zc95Protocol.spec.ts b/tests/unit/device/protocol/zc95/zc95Protocol.spec.ts index 02411678..65a21d6f 100644 --- a/tests/unit/device/protocol/zc95/zc95Protocol.spec.ts +++ b/tests/unit/device/protocol/zc95/zc95Protocol.spec.ts @@ -1,10 +1,16 @@ +import { Ajv2020 } from 'ajv/dist/2020.js'; +import ajvFormatsPlugin from 'ajv-formats'; import { describe, expect, it } from 'vitest'; import Zc95Protocol from '../../../../../src/device/protocol/zc95/zc95Protocol.js'; import { expectToBeErrorDecodeResult, expectToBeSuccessfulDecodeResult } from '../../../helper/protocol.js'; +import JsonSchemaValidatorFactory from '../../../../../src/schemaValidation/JsonSchemaValidatorFactory.js'; describe('Zc95Protocol', () => { + const ajv = new Ajv2020({ allErrors: true, strict: true }); + ajvFormatsPlugin.default(ajv); - const protocol = new Zc95Protocol(); + const jsonSchemaValidatorFactory = new JsonSchemaValidatorFactory(ajv); + const protocol = new Zc95Protocol(jsonSchemaValidatorFactory); describe('encode', () => { it('serializes the message object to a JSON Buffer', () => { diff --git a/tests/unit/serial/frameParser.spec.ts b/tests/unit/serial/frameParser.spec.ts index af27d459..239a80ff 100644 --- a/tests/unit/serial/frameParser.spec.ts +++ b/tests/unit/serial/frameParser.spec.ts @@ -24,7 +24,7 @@ describe('FrameParser', () => { ); expect(frames).toHaveLength(1); - expect(frames[0].toString()).toBe('hello world'); + expect(frames[0]?.toString()).toBe('hello world'); }); it('parses a frame split across multiple chunks', () => { @@ -36,7 +36,7 @@ describe('FrameParser', () => { ); expect(frames).toHaveLength(1); - expect(frames[0].toString()).toBe('hello'); + expect(frames[0]?.toString()).toBe('hello'); }); it('parses multiple frames in a single chunk', () => { @@ -47,8 +47,8 @@ describe('FrameParser', () => { ); expect(frames).toHaveLength(2); - expect(frames[0].toString()).toBe('foo'); - expect(frames[1].toString()).toBe('bar'); + expect(frames[0]?.toString()).toBe('foo'); + expect(frames[1]?.toString()).toBe('bar'); }); it('ignores bytes outside of STX/ETX boundaries', () => { @@ -59,7 +59,7 @@ describe('FrameParser', () => { ); expect(frames).toHaveLength(1); - expect(frames[0].toString()).toBe('hello'); + expect(frames[0]?.toString()).toBe('hello'); }); it('emits an error and destroys when frame exceeds maxMessageSize', async () => { @@ -85,7 +85,7 @@ describe('FrameParser', () => { ); expect(frames).toHaveLength(1); - expect(frames[0].byteLength).toBe(0); + expect(frames[0]?.byteLength).toBe(0); }); it('ignores incomplete frame on flush', () => { diff --git a/tsconfig.json b/tsconfig.json index 72ffcc40..06a2041c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "moduleResolution": "NodeNext", "target": "ES2022", "noImplicitOverride": true, + "noUncheckedIndexedAccess": true, "allowSyntheticDefaultImports": true, "sourceMap": true, "experimentalDecorators": true, From 06f87093e101c0e8b33363789050391aa2fa6be7 Mon Sep 17 00:00:00 2001 From: HRS Date: Fri, 7 Aug 2026 08:52:11 +0200 Subject: [PATCH 05/15] Fix linting --- src/device/protocol/virtual/virtualDevice.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/device/protocol/virtual/virtualDevice.ts b/src/device/protocol/virtual/virtualDevice.ts index f7b4178a..09dda7fd 100644 --- a/src/device/protocol/virtual/virtualDevice.ts +++ b/src/device/protocol/virtual/virtualDevice.ts @@ -1,5 +1,4 @@ import { Exclude, Expose } from 'class-transformer'; -import BaseError from 'modern-errors'; import Device, { AttributeKeyOf, AttributeValueOf, NoDeviceNotifications } from '../../device.js'; import DeviceState from '../../deviceState.js'; import VirtualDeviceLogic, { AnyVirtualDeviceLogic, ExtractAttributes, ExtractConfig } from './virtualDeviceLogic.js'; From 87cbe83344df21c40ad7c99e717474b4b0c54d9a Mon Sep 17 00:00:00 2001 From: HRS Date: Fri, 7 Aug 2026 17:03:54 +0200 Subject: [PATCH 06/15] Fix flaky attribute-set test racing against periodic device refresh waitForNextWsEvent(deviceRefreshed) had no predicate, so it could resolve on the device's own 175ms auto-refresh tick instead of the refresh actually caused by the channelALevel update, letting the assertion run before 'A80' was written to the device. --- tests/integration/devices/estim2bDevice.spec.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tests/integration/devices/estim2bDevice.spec.ts b/tests/integration/devices/estim2bDevice.spec.ts index 866bfb1c..6e431242 100644 --- a/tests/integration/devices/estim2bDevice.spec.ts +++ b/tests/integration/devices/estim2bDevice.spec.ts @@ -152,7 +152,12 @@ describe('E-Stim Systems 2B serial device provider', () => { expect(resAfterPatch.status).toBe(200); expect(resAfterPatch.body.attributes.channelALevel.value).toBe(50); - const deviceRefreshed = waitForNextWsEvent(wsEmitSpy, WebSocketEvent.deviceRefreshed); + const deviceRefreshed = waitForNextWsEvent( + wsEmitSpy, + WebSocketEvent.deviceRefreshed, + 5000, + ([device]) => device.attributes.channelALevel?.value === 80, + ); wsClient.emit(WebSocketEvent.deviceUpdateReceived, { deviceId, data: { channelALevel: 80 } }); await deviceRefreshed; From a8a3c561f0627ed6d255e7f88d775217097a8859 Mon Sep 17 00:00:00 2001 From: HRS Date: Fri, 7 Aug 2026 17:04:32 +0200 Subject: [PATCH 07/15] Remove unsued imports --- tests/integration/helpers/appHelper.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/integration/helpers/appHelper.ts b/tests/integration/helpers/appHelper.ts index aa5f0d45..757b1268 100644 --- a/tests/integration/helpers/appHelper.ts +++ b/tests/integration/helpers/appHelper.ts @@ -3,7 +3,6 @@ import os from 'os'; import path from 'path'; import { io as ioClient } from 'socket.io-client'; import { createApp, AppInstance, createContainer, AppOptions } from '../../../src/app.js'; -import { AnyDevice } from '../../../src/device/device.js'; import { DeviceManagerEvent } from '../../../src/device/deviceManager.js'; import { ServerToClientEvents } from '../../../src/socket/types.js'; type WsEmitCall = { [E in keyof ServerToClientEvents]: [E, ...Parameters] }[keyof ServerToClientEvents]; @@ -14,7 +13,6 @@ import ServiceMap from '../../../src/serviceMap.js'; import { Container } from '@timesplinter/pimple'; import MockSerialPortFactory from './mockSerialPortFactory.js'; import http from 'http'; -import { AddressInfo } from 'net'; import { DeviceId } from '../../../src/device/deviceId.js'; process.env.LOG_LEVEL = process.env.LOG_LEVEL ?? 'silent'; From 2d4411b8605dc857984d8ff8cfd0c923b3081dfb Mon Sep 17 00:00:00 2001 From: HRS Date: Fri, 7 Aug 2026 17:58:38 +0200 Subject: [PATCH 08/15] Fix unhandled rejection from isolate disposal race in ScriptVm --- src/automation/scriptVm.ts | 15 +++++++++++++-- src/automation/scriptVmFactory.ts | 5 ++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/automation/scriptVm.ts b/src/automation/scriptVm.ts index fa9b1135..1bbdf059 100644 --- a/src/automation/scriptVm.ts +++ b/src/automation/scriptVm.ts @@ -1,5 +1,6 @@ import ivm from 'isolated-vm'; import { EventEmitter } from 'events'; +import Logger from '../logging/Logger.js'; export type ScriptVmSignalEvents = { eventDone: [errMsg: string | null]; @@ -23,18 +24,22 @@ export default class ScriptVm private readonly signals: EventEmitter; + private readonly logger: Logger; + public constructor( isolate: ivm.Isolate, vmContext: ivm.Context, dispatchRef: ivm.Reference, lifecycleRef: ivm.Reference, signals: EventEmitter, + logger: Logger, ) { this.isolate = isolate; this.vmContext = vmContext; this.dispatchRef = dispatchRef; this.lifecycleRef = lifecycleRef; this.signals = signals; + this.logger = logger.child({ name: ScriptVm.name }); } public start(): Promise @@ -51,7 +56,10 @@ export default class ScriptVm public dispatchEvent(eventType: string, deviceJson: string, args: unknown): Promise { const done = this.waitFor('eventDone'); - void this.dispatchRef.apply(undefined, [eventType, deviceJson, args], { arguments: { copy: true } }); + // Completion is signalled via the 'eventDone' channel above; this catch only prevents an + // unhandled rejection if the isolate is disposed while the call is still in flight. + this.dispatchRef.apply(undefined, [eventType, deviceJson, args], { arguments: { copy: true } }) + .catch((e: unknown) => this.logger.debug('dispatchEvent call into isolate failed', e)); return done; } @@ -75,7 +83,10 @@ export default class ScriptVm private dispatchLifecycle(phase: LifecyclePhase): Promise { const done = this.waitFor('lifecycleDone'); - void this.lifecycleRef.apply(undefined, [phase], { arguments: { copy: true } }); + // Completion is signalled via the 'lifecycleDone' channel above; this catch only prevents + // an unhandled rejection if the isolate is disposed while the call is still in flight. + this.lifecycleRef.apply(undefined, [phase], { arguments: { copy: true } }) + .catch((e: unknown) => this.logger.debug('dispatchLifecycle call into isolate failed', e)); return done; } diff --git a/src/automation/scriptVmFactory.ts b/src/automation/scriptVmFactory.ts index d2739375..42872eae 100644 --- a/src/automation/scriptVmFactory.ts +++ b/src/automation/scriptVmFactory.ts @@ -185,9 +185,12 @@ export default class ScriptVmFactory private readonly automationScriptLogger: Logger; + private readonly logger: Logger; + public constructor(deviceRepository: DeviceRepositoryInterface, logger: Logger) { this.deviceRepository = deviceRepository; this.automationScriptLogger = logger.child({ name: 'AutomationScript' }); + this.logger = logger.child({ name: ScriptVmFactory.name }); } public async create(scriptCode: string, onConsoleLog: (message: string) => void): Promise @@ -265,7 +268,7 @@ export default class ScriptVmFactory throw new Error(`Expected '${VM_REF_DISPATCH_LIFECYCLE}' to be an ivm.Reference`); } - return new ScriptVm(isolate, vmContext, dispatchRef, lifecycleRef, signals); + return new ScriptVm(isolate, vmContext, dispatchRef, lifecycleRef, signals, this.logger); } catch (e) { vmContext?.release(); isolate.dispose(); From 0523829e7148b77c6f5137a43d523be30528a22d Mon Sep 17 00:00:00 2001 From: HRS Date: Fri, 7 Aug 2026 18:29:04 +0200 Subject: [PATCH 09/15] Fix double logger.child() causing undefined logger in ScriptVm --- src/automation/scriptVmFactory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/automation/scriptVmFactory.ts b/src/automation/scriptVmFactory.ts index 42872eae..ec951f51 100644 --- a/src/automation/scriptVmFactory.ts +++ b/src/automation/scriptVmFactory.ts @@ -190,7 +190,7 @@ export default class ScriptVmFactory public constructor(deviceRepository: DeviceRepositoryInterface, logger: Logger) { this.deviceRepository = deviceRepository; this.automationScriptLogger = logger.child({ name: 'AutomationScript' }); - this.logger = logger.child({ name: ScriptVmFactory.name }); + this.logger = logger; } public async create(scriptCode: string, onConsoleLog: (message: string) => void): Promise From f983d04e6a8c731aa3ba0c3a53853e3dfbf7a201 Mon Sep 17 00:00:00 2001 From: HRS Date: Fri, 7 Aug 2026 18:43:45 +0200 Subject: [PATCH 10/15] Remove duplicate/no-op eslint rules already covered by presets --- eslint.config.ts | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/eslint.config.ts b/eslint.config.ts index 927416c1..b86c3ece 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -69,8 +69,6 @@ export default [ "assertionStyle": "never", } ], - "@typescript-eslint/no-unnecessary-type-assertion": "error", - "@typescript-eslint/dot-notation": "error", "@typescript-eslint/explicit-function-return-type": "error", "@typescript-eslint/explicit-member-accessibility": [ "error", @@ -92,22 +90,18 @@ export default [ "leadingUnderscore": "allow" } ], - "@typescript-eslint/no-empty-interface": "off", "@typescript-eslint/no-floating-promises": [ "warn", { "checkThenables": true } ], - "@typescript-eslint/unbound-method": "error", - "@typescript-eslint/no-misused-promises": "error", "@typescript-eslint/consistent-type-definitions": [ "error", "type" ], "@typescript-eslint/parameter-properties": "error", "@typescript-eslint/no-shadow": "error", - "@typescript-eslint/no-unused-expressions": "error", "@typescript-eslint/no-use-before-define": "error", "@typescript-eslint/restrict-template-expressions": [ "warn", @@ -115,7 +109,6 @@ export default [ "allowNumber": true } ], - "@typescript-eslint/typedef": "off", "@typescript-eslint/strict-boolean-expressions": "error", "@stylistic/indent": ["error", 4], "@stylistic/semi": ["error", "always"], @@ -153,7 +146,6 @@ export default [ "@jsdoc/check-indentation": "error", "@jsdoc/tag-lines": "error", "complexity": "error", - "dot-notation": "off", "eqeqeq": [ "error", "smart" @@ -178,9 +170,7 @@ export default [ "no-console": "warn", "no-eval": "error", "no-fallthrough": "off", - "no-invalid-this": "off", "no-new-wrappers": "error", - "no-shadow": "off", "no-throw-literal": "error", "no-undef-init": "error", "no-underscore-dangle": [ @@ -190,19 +180,15 @@ export default [ } ], "no-unused-expressions": "error", - "no-use-before-define": "off", "no-implicit-coercion": ["error", { "boolean": true, "number": true, "string": true }], - "no-var": "error", - "object-shorthand": "off", "one-var": [ "error", "never" ], - "prefer-const": "error", "radix": "error", "valid-typeof": "off", }, From 1d36813b3933153af6dd9f284cced10cf6e7d888 Mon Sep 17 00:00:00 2001 From: HRS Date: Fri, 7 Aug 2026 18:46:03 +0200 Subject: [PATCH 11/15] Drop base rules superseded by type-aware equivalents (no-throw-literal, no-unused-expressions) --- eslint.config.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/eslint.config.ts b/eslint.config.ts index b86c3ece..afb25a6b 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -171,7 +171,6 @@ export default [ "no-eval": "error", "no-fallthrough": "off", "no-new-wrappers": "error", - "no-throw-literal": "error", "no-undef-init": "error", "no-underscore-dangle": [ "error", @@ -179,7 +178,6 @@ export default [ "allowAfterThis": true } ], - "no-unused-expressions": "error", "no-implicit-coercion": ["error", { "boolean": true, "number": true, From 369c9e3b59c3ab985deb4d0d01d0b36b345330fd Mon Sep 17 00:00:00 2001 From: HRS Date: Fri, 7 Aug 2026 19:17:25 +0200 Subject: [PATCH 12/15] Re-enable valid-typeof, simplify explicit-member-accessibility to default options --- eslint.config.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/eslint.config.ts b/eslint.config.ts index afb25a6b..2a4c02cc 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -70,12 +70,7 @@ export default [ } ], "@typescript-eslint/explicit-function-return-type": "error", - "@typescript-eslint/explicit-member-accessibility": [ - "error", - { - "accessibility": "explicit" - } - ], + "@typescript-eslint/explicit-member-accessibility": "error", "@typescript-eslint/explicit-module-boundary-types": "error", "@typescript-eslint/naming-convention": [ "error", @@ -188,7 +183,6 @@ export default [ "never" ], "radix": "error", - "valid-typeof": "off", }, }, ]; From 3f6bc203b29eb2e614780b2d5272167d969349ad Mon Sep 17 00:00:00 2001 From: HRS Date: Sat, 8 Aug 2026 21:57:36 +0200 Subject: [PATCH 13/15] Even more stricterer than strict linting, wip --- eslint.config.ts | 167 ++++++++++++++--- package-lock.json | 21 +++ package.json | 4 +- src/app.ts | 38 ++-- src/automation/scriptRuntime.ts | 86 ++++----- src/automation/scriptVm.ts | 16 +- src/automation/scriptVmFactory.ts | 24 +-- .../automation/createScriptController.ts | 13 +- .../automation/deleteScriptController.ts | 11 +- src/controller/automation/getLogController.ts | 15 +- .../automation/getScriptController.ts | 13 +- .../automation/getScriptsController.ts | 10 +- .../automation/runScriptController.ts | 11 +- .../automation/statusScriptController.ts | 9 +- .../automation/stopScriptController.ts | 9 +- src/controller/controllerInterface.ts | 6 +- src/controller/getDeviceController.ts | 17 +- src/controller/getDevicesController.ts | 12 +- src/controller/healthController.ts | 9 +- src/controller/patchDeviceController.ts | 23 +-- .../settings/getSettingsController.ts | 15 +- .../settings/putSettingsController.ts | 24 +-- src/controller/versionController.ts | 4 +- src/device/attribute/boolDeviceAttribute.ts | 10 +- src/device/attribute/deviceAttribute.ts | 11 +- src/device/attribute/floatDeviceAttribute.ts | 5 +- src/device/attribute/intDeviceAttribute.ts | 5 +- .../attribute/intRangeDeviceAttribute.ts | 7 +- src/device/attribute/listDeviceAttribute.ts | 12 +- src/device/attribute/numberDeviceAttribute.ts | 7 +- src/device/attribute/strDeviceAttribute.ts | 10 +- src/device/bleDevice.ts | 67 +++---- src/device/detectedDeviceOfferQueue.ts | 129 ++++++------- src/device/device.ts | 84 +++++---- src/device/deviceConfig.ts | 3 +- src/device/deviceId.ts | 5 +- src/device/deviceManager.ts | 146 ++++++++------- src/device/deviceNameGenerator.ts | 3 +- src/device/genericDeviceUpdater.ts | 8 +- src/device/knownDeviceRegistry.ts | 8 +- src/device/peripheralDevice.ts | 23 +-- src/device/protocol/airotic/airoticDevice.ts | 165 +++++++++-------- .../protocol/airotic/airoticDeviceFactory.ts | 56 +++--- .../protocol/airotic/airoticDeviceProvider.ts | 22 ++- .../protocol/airotic/airoticProtocol.ts | 39 ++-- .../protocol/buttplugIo/buttplugIoDevice.ts | 91 ++++----- .../buttplugIo/buttplugIoDeviceFactory.ts | 53 +++--- .../buttplugIoWebsocketDeviceProvider.ts | 47 ++--- ...uttplugIoWebsocketDeviceProviderFactory.ts | 8 +- src/device/protocol/deviceProtocol.ts | 2 + src/device/protocol/estim2b/estim2bDevice.ts | 153 +++++++-------- .../protocol/estim2b/estim2bDeviceFactory.ts | 47 +++-- .../protocol/estim2b/estim2bProtocol.ts | 51 ++--- .../estim2b/estim2bSerialDeviceProvider.ts | 22 ++- src/device/protocol/messageResponseHandler.ts | 113 +++++------ .../slvCtrlPlus/genericSlvCtrlPlusDevice.ts | 65 ++++--- .../protocol/slvCtrlPlus/slvCtrlPlusDevice.ts | 24 +-- .../slvCtrlPlus/slvCtrlPlusDeviceFactory.ts | 55 +++--- .../slvCtrlPlusSerialDeviceProvider.ts | 76 ++++---- .../protocol/slvCtrlPlus/slvCtrlProtocol.ts | 19 +- .../slvCtrlPlus/slvCtrlProtocolLegacy.ts | 129 ++++--------- .../protocol/slvCtrlPlus/slvCtrlProtocolV1.ts | 38 ++-- .../virtual/audio/piperVirtualDeviceConfig.ts | 3 +- .../virtual/audio/piperVirtualDeviceLogic.ts | 47 ++--- .../virtual/audio/ttsVirtualDeviceConfig.ts | 3 +- .../virtual/audio/ttsVirtualDeviceLogic.ts | 12 +- .../display/displayVirtualDeviceLogic.ts | 12 +- .../virtual/genericVirtualDeviceFactory.ts | 37 ++-- .../genericVirtualDeviceLogicFactory.ts | 7 +- .../randomGeneratorVirtualDeviceConfig.ts | 3 +- .../randomGeneratorVirtualDeviceLogic.ts | 12 +- src/device/protocol/virtual/virtualDevice.ts | 62 +++---- .../protocol/virtual/virtualDeviceFactory.ts | 4 +- .../protocol/virtual/virtualDeviceLogic.ts | 15 +- .../virtual/virtualDeviceLogicFactory.ts | 3 +- .../protocol/virtual/virtualDeviceProvider.ts | 18 +- .../virtual/virtualDeviceProviderFactory.ts | 10 +- src/device/protocol/zc95/zc95Device.ts | 175 +++++++++--------- src/device/protocol/zc95/zc95DeviceFactory.ts | 46 +++-- .../protocol/zc95/zc95MessageFactory.ts | 3 +- src/device/protocol/zc95/zc95Protocol.ts | 42 +++-- .../protocol/zc95/zc95SerialDeviceProvider.ts | 28 +-- src/device/provider/bleDeviceProvider.ts | 22 ++- src/device/provider/deviceProvider.ts | 33 ++-- src/device/provider/deviceProviderFactory.ts | 4 +- src/device/provider/deviceProviderManager.ts | 28 +-- .../provider/genericDeviceProviderFactory.ts | 4 +- src/device/provider/serialDeviceProvider.ts | 24 +-- src/device/serializedTypes.ts | 6 +- src/device/transport/bleDeviceTransport.ts | 140 +++++++------- src/device/transport/bleObserver.ts | 13 +- .../transport/deviceBidirectionalTransport.ts | 6 +- .../transport/deviceReadableTransport.ts | 10 +- src/device/transport/serialDeviceTransport.ts | 4 +- .../transport/serialDeviceTransportFactory.ts | 3 +- src/device/transport/serialPortObserver.ts | 106 ++++++----- src/device/transport/sharedObserver.ts | 21 ++- src/device/updater/abstractDeviceUpdater.ts | 6 +- src/device/updater/bufferedDeviceUpdater.ts | 12 +- src/device/updater/deviceUpdaterInterface.ts | 4 +- src/entity/deviceList.ts | 3 +- src/entity/list.ts | 4 +- src/env.ts | 3 +- src/factory/dateFactory.ts | 1 + src/factory/eventEmitterFactory.ts | 1 + src/factory/serialPortFactory.ts | 8 +- src/health/healthMetricsCollector.ts | 22 ++- src/health/serializedTypes.ts | 2 +- src/index.ts | 3 +- src/logging/Logger.ts | 14 +- src/logging/PinoLogger.ts | 7 +- src/middleware/contentTypeMiddleware.ts | 2 +- src/repository/automationScriptRepository.ts | 2 +- .../automationScriptRepositoryInterface.ts | 10 +- src/repository/connectedDeviceRepository.ts | 8 +- src/repository/deviceRepositoryInterface.ts | 7 +- src/schemaValidation/JsonSchemaValidator.ts | 4 +- .../JsonSchemaValidatorFactory.ts | 4 +- src/schemaValidation/schemaValidationError.ts | 2 +- src/serial/frameParser.ts | 7 +- src/serial/synchronousSerialPort.ts | 79 ++++---- src/serialization/classToPlainSerializer.ts | 3 +- .../discriminator/objectDiscriminator.ts | 2 +- src/serialization/plainToClassSerializer.ts | 7 +- src/serviceMap.ts | 108 +++++------ .../automationServiceProvider.ts | 4 +- .../controllerServiceProvider.ts | 4 +- src/serviceProvider/deviceServiceProvider.ts | 11 +- src/serviceProvider/factoryServiceProvider.ts | 4 +- src/serviceProvider/healthServiceProvider.ts | 4 +- src/serviceProvider/loggerServiceProvider.ts | 6 +- .../repositoryServiceProvider.ts | 4 +- .../schemaValidationServiceProvider.ts | 4 +- .../serializationServiceProvider.ts | 4 +- .../settingsServiceProvider.ts | 4 +- src/serviceProvider/socketServiceProvider.ts | 4 +- src/settings/deviceSource.ts | 2 +- src/settings/knownDevice.ts | 4 +- src/settings/serializedTypes.ts | 2 +- src/settings/settings.ts | 2 +- src/settings/settingsManager.ts | 16 +- src/socket/deviceUpdateHandler.ts | 8 +- src/socket/types.ts | 18 +- src/util/async.ts | 22 ++- src/util/color.ts | 35 ++-- src/util/devNullStream.ts | 10 +- src/util/error.ts | 2 +- src/util/expressUtils.ts | 4 +- src/util/json.ts | 4 +- src/util/latestOnlyTaskQueue.ts | 11 +- src/util/numbers.ts | 9 +- src/util/objects.ts | 2 +- src/util/process.ts | 31 ++-- tests/unit/automation/scriptRuntime.spec.ts | 2 +- tests/unit/device/bleDevice.spec.ts | 2 +- .../protocol/airotic/airoticDevice.spec.ts | 14 +- .../buttplugIo/buttplugIoDevice.spec.ts | 11 +- .../protocol/estim2b/estim2bDevice.spec.ts | 12 +- .../genericSlvCtrlPlusDevice.spec.ts | 58 +++--- .../display/displayVirtualDevice.spec.ts | 11 +- .../device/protocol/zc95/zc95Device.spec.ts | 12 +- .../device/provider/deviceProvider.spec.ts | 2 +- tests/unit/device/testDevice.ts | 2 +- tsconfig.tooling.json | 14 ++ types/eslint-plugin-sort-class-members.d.ts | 1 + 165 files changed, 2205 insertions(+), 1847 deletions(-) create mode 100644 tsconfig.tooling.json create mode 100644 types/eslint-plugin-sort-class-members.d.ts diff --git a/eslint.config.ts b/eslint.config.ts index 2a4c02cc..4fb38ca7 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -1,29 +1,22 @@ -import tseslint from "typescript-eslint"; -import eslint from "@eslint/js"; -import jsdoc from "eslint-plugin-jsdoc"; -import preferArrowFunctions from "eslint-plugin-prefer-arrow-functions"; +import { defineConfig, globalIgnores } from 'eslint/config'; +import tseslint from 'typescript-eslint'; +import eslint from '@eslint/js'; +import jsdoc from 'eslint-plugin-jsdoc'; +import preferArrowFunctions from 'eslint-plugin-prefer-arrow-functions'; import globals from 'globals'; -import stylistic from "@stylistic/eslint-plugin"; +import stylistic from '@stylistic/eslint-plugin'; +import sortClassMembers from "eslint-plugin-sort-class-members"; -export default [ +export default defineConfig([ + globalIgnores(['**/*.js', '**/*.cjs', '**/*.mjs']), { - ignores: [ - 'node_modules', - 'dist', - 'coverage', - 'tests', - 'vitest.config.ts', - 'vitest.config.unit.ts', - 'vitest.config.integration.ts', - 'eslint.config.ts', + files: ['src/**/*.ts'], + extends: [ + eslint.configs.recommended, + tseslint.configs.all, + stylistic.configs.recommended, + preferArrowFunctions.configs.all, ], - }, - eslint.configs.recommended, - ...tseslint.configs.strictTypeChecked, - ...tseslint.configs.stylisticTypeChecked, - stylistic.configs.recommended, - preferArrowFunctions.configs.all, - { languageOptions: { parserOptions: { project: './tsconfig.json', @@ -35,8 +28,21 @@ export default [ }, plugins: { "@jsdoc": jsdoc, + "@sortClassMembers": sortClassMembers, }, rules: { + // Extremely slow (deep readonly type-checking); can hang lint on this codebase. + "@typescript-eslint/prefer-readonly-parameter-types": "off", + "@typescript-eslint/no-magic-numbers": ["error", { + "ignoreReadonlyClassProperties": true, + "ignoreTypeIndexes": true, + "ignoreEnums": true, + "ignoreNumericLiteralTypes": true, + "ignoreArrayIndexes": true, + "ignoreDefaultValues": true, + "ignoreClassFieldInitialValues": true, + "ignore": [-1, 0, 1, 10], + }], "@typescript-eslint/no-confusing-void-expression": [ "error", { @@ -105,8 +111,23 @@ export default [ } ], "@typescript-eslint/strict-boolean-expressions": "error", + "@typescript-eslint/class-methods-use-this": [ + "error", + { + "ignoreOverrideMethods": true, + "ignoreClassesThatImplementAnInterface": true, + } + ], + "@typescript-eslint/max-params": [ + "error", + { + "max": 10 + } + ], + "@typescript-eslint/prefer-destructuring": "off", + "@typescript-eslint/member-ordering": "off", "@stylistic/indent": ["error", 4], - "@stylistic/semi": ["error", "always"], + "@stylistic/semi": ["error", "always", { "omitLastInOneLineBlock": true }], "@stylistic/brace-style": "off", "@stylistic/member-delimiter-style": [ "error", @@ -140,6 +161,91 @@ export default [ "@jsdoc/check-alignment": "error", "@jsdoc/check-indentation": "error", "@jsdoc/tag-lines": "error", + '@sortClassMembers/sort-class-members': ['error', { + order: [ + '[public-static-readonly-properties]', + '[protected-static-readonly-properties]', + '[private-static-readonly-properties]', + + '[public-static-properties]', + '[public-properties]', + '[protected-static-properties]', + '[protected-properties]', + '[private-static-properties]', + '[private-properties]', + + 'constructor', + + '[public-static-methods]', + '[public-instance-methods]', + '[public-abstract-methods]', + + '[protected-instance-methods]', + '[protected-abstract-methods]', + '[protected-static-methods]', + + '[private-instance-methods]', + '[private-static-methods]', // no private-abstract: TS disallows private abstract members + ], + groups: { + 'public-static-readonly-properties': [ + { type: 'property', accessibility: 'public', static: true, readonly: true }, + ], + 'protected-static-readonly-properties': [ + { type: 'property', accessibility: 'protected', static: true, readonly: true }, + ], + 'private-static-readonly-properties': [ + { type: 'property', accessibility: 'private', static: true, readonly: true }, + ], + + 'public-static-properties': [ + { type: 'property', accessibility: 'public', static: true, readonly: false }, + ], + 'protected-static-properties': [ + { type: 'property', accessibility: 'protected', static: true, readonly: false }, + ], + 'private-static-properties': [ + { type: 'property', accessibility: 'private', static: true, readonly: false }, + ], + + 'public-properties': [ + { type: 'property', accessibility: 'public', static: false }, + ], + 'protected-properties': [ + { type: 'property', accessibility: 'protected', static: false }, + ], + 'private-properties': [ + { type: 'property', accessibility: 'private', static: false }, + ], + + 'public-instance-methods': [ + { type: 'method', accessibility: 'public', static: false, abstract: false }, + ], + 'public-abstract-methods': [ + { type: 'method', accessibility: 'public', abstract: true }, + ], + 'public-static-methods': [ + { type: 'method', accessibility: 'public', static: true }, + ], + + 'protected-instance-methods': [ + { type: 'method', accessibility: 'protected', static: false, abstract: false }, + ], + 'protected-abstract-methods': [ + { type: 'method', accessibility: 'protected', abstract: true }, + ], + 'protected-static-methods': [ + { type: 'method', accessibility: 'protected', static: true }, + ], + + 'private-instance-methods': [ + { type: 'method', accessibility: 'private', static: false }, + ], + 'private-static-methods': [ + { type: 'method', accessibility: 'private', static: true }, + ], + }, + }], "complexity": "error", "eqeqeq": [ "error", @@ -165,6 +271,7 @@ export default [ "no-console": "warn", "no-eval": "error", "no-fallthrough": "off", + "no-magic-numbers": "off", "no-new-wrappers": "error", "no-undef-init": "error", "no-underscore-dangle": [ @@ -185,4 +292,16 @@ export default [ "radix": "error", }, }, -]; + { + files: ['src/**/*.js'], + rules: { + 'no-restricted-syntax': [ + 'error', + { + selector: 'Program', + message: 'JavaScript files are not allowed in this project; use TypeScript (.ts) instead.', + }, + ], + }, + }, +]); diff --git a/package-lock.json b/package-lock.json index fc3dc035..40b97444 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "cors": "^2.8.5", "dotenv": "^17.2.0", "express": "^5.2.1", + "http-status-codes": "^2.3.0", "isolated-vm": "^6.0.2", "modern-errors": "^7.1.4", "node-os-utils": "^2.0.3", @@ -52,6 +53,7 @@ "eslint": "^10.1.0", "eslint-plugin-jsdoc": "^62.8.0", "eslint-plugin-prefer-arrow-functions": "^3.10.1", + "eslint-plugin-sort-class-members": "^1.22.1", "globals": "^17.0.0", "jiti": "^2.6.1", "knip": "^6.31.0", @@ -3947,6 +3949,19 @@ "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0" } }, + "node_modules/eslint-plugin-sort-class-members": { + "version": "1.22.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-sort-class-members/-/eslint-plugin-sort-class-members-1.22.1.tgz", + "integrity": "sha512-TC76+m06fjpiAYPrEcyyvvWzp1aMHNBVGROLu9ijQuPDZPWJjhNTheha4f6f7eUTWabloczLZagoZ+mb6Cv0ZQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + }, + "peerDependencies": { + "eslint": ">=0.8.0" + } + }, "node_modules/eslint-scope": { "version": "9.1.2", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-9.1.2.tgz", @@ -4694,6 +4709,12 @@ "url": "https://opencollective.com/express" } }, + "node_modules/http-status-codes": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/http-status-codes/-/http-status-codes-2.3.0.tgz", + "integrity": "sha512-RJ8XvFvpPM/Dmc5SV+dC4y5PCeOhT3x1Hq0NU3rjGeg5a/CqlhZ7uudknPwZFz4aeAXDcbAyaeP7GAo9lvngtA==", + "license": "MIT" + }, "node_modules/iconv-lite": { "version": "0.7.3", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.3.tgz", diff --git a/package.json b/package.json index 2e819a49..888ca292 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "cors": "^2.8.5", "dotenv": "^17.2.0", "express": "^5.2.1", + "http-status-codes": "^2.3.0", "isolated-vm": "^6.0.2", "modern-errors": "^7.1.4", "node-os-utils": "^2.0.3", @@ -50,6 +51,7 @@ "eslint": "^10.1.0", "eslint-plugin-jsdoc": "^62.8.0", "eslint-plugin-prefer-arrow-functions": "^3.10.1", + "eslint-plugin-sort-class-members": "^1.22.1", "globals": "^17.0.0", "jiti": "^2.6.1", "knip": "^6.31.0", @@ -73,7 +75,7 @@ "test:unit": "vitest run --project unit", "test:integration": "vitest run --project integration", "test:coverage": "vitest run --project unit --coverage", - "typecheck": "tsc --noEmit && tsc --noEmit -p tests/tsconfig.json", + "typecheck": "tsc --noEmit -p tsconfig.tooling.json && tsc --noEmit && tsc --noEmit -p tests/tsconfig.json", "lint": "eslint -c eslint.config.ts .", "knip": "knip", "check": "npm run typecheck && npm run lint && npm run knip", diff --git a/src/app.ts b/src/app.ts index ea16d6b0..e6d4454c 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,7 +1,9 @@ -import cors, { CorsOptions } from 'cors'; +import type { CorsOptions } from 'cors'; +import cors from 'cors'; import contentTypeMiddleware from './middleware/contentTypeMiddleware.js'; import express from 'express'; -import { Container, Pimple } from '@timesplinter/pimple'; +import type { Container } from '@timesplinter/pimple'; +import { Pimple } from '@timesplinter/pimple'; import ControllerServiceProvider from './serviceProvider/controllerServiceProvider.js'; import RepositoryServiceProvider from './serviceProvider/repositoryServiceProvider.js'; import SerializationServiceProvider from './serviceProvider/serializationServiceProvider.js'; @@ -10,16 +12,16 @@ import DeviceServiceProvider from './serviceProvider/deviceServiceProvider.js'; import SettingsServiceProvider from './serviceProvider/settingsServiceProvider.js'; import SchemaValidationServiceProvider from './serviceProvider/schemaValidationServiceProvider.js'; import SocketServiceProvider from './serviceProvider/socketServiceProvider.js'; -import { ClientToServerEvents, ServerToClientEvents, WebsocketServer } from './socket/types.js'; -import { SerializedDevice } from './device/serializedTypes.js'; -import { SerializedSettings } from './settings/serializedTypes.js'; +import type { ClientToServerEvents, ServerToClientEvents, WebsocketServer } from './socket/types.js'; +import type { SerializedDevice } from './device/serializedTypes.js'; +import type { SerializedSettings } from './settings/serializedTypes.js'; import AutomationServiceProvider from './serviceProvider/automationServiceProvider.js'; -import { AnyDevice } from './device/device.js'; +import type { AnyDevice } from './device/device.js'; import WebSocketEvent from './device/webSocketEvent.js'; import AutomationEventType from './automation/automationEventType.js'; import LoggerServiceProvider from './serviceProvider/loggerServiceProvider.js'; import deviceDiscriminator from './serialization/discriminator/deviceDiscriminator.js'; -import ServiceMap from './serviceMap.js'; +import type ServiceMap from './serviceMap.js'; import SettingsEventType from './settings/settingsEventType.js'; import type Settings from './settings/settings.js'; import { executeController } from './util/expressUtils.js'; @@ -27,7 +29,7 @@ import { DeviceManagerEvent } from './device/deviceManager.js'; import HealthServiceProvider from './serviceProvider/healthServiceProvider.js'; import { logError } from './util/error.js'; import { HealthMetricsCollectorEvent } from './health/healthMetricsCollector.js'; -import { SerializedHealthMetrics } from './health/serializedTypes.js'; +import type { SerializedHealthMetrics } from './health/serializedTypes.js'; import http from 'http'; import https from 'https'; import fs from 'fs'; @@ -80,6 +82,8 @@ const configureRoutes = (app: express.Application, container: Container): void => { + const healthMetricsCollectorIntervalMs = 1000; + const deviceManager = container.get('device.manager'); const scriptRuntime = container.get('automation.scriptRuntime'); const settingsManager = container.get('settings.manager'); @@ -90,7 +94,7 @@ const configureWebsocket = (io: WebsocketServer, container: Container { io.emit(WebSocketEvent.healthMetrics, metrics); }); @@ -105,7 +109,9 @@ const configureWebsocket = (io: WebsocketServer, container: Container deviceUpdateHandler.handle(data)); + socket.on(WebSocketEvent.deviceUpdateReceived, data => { + void deviceUpdateHandler.handle(data); + }); }); deviceManager.on(DeviceManagerEvent.deviceConnected, (device: AnyDevice) => { @@ -133,7 +139,9 @@ const configureWebsocket = (io: WebsocketServer, container: Container io.emit(AutomationEventType.consoleLog, data)); + scriptRuntime.on(AutomationEventType.consoleLog, (data: string) => { + io.emit(AutomationEventType.consoleLog, data); + }); }; const startDeviceProviders = (container: Container): void => { @@ -151,9 +159,9 @@ const startDeviceProviders = (container: Container): void => { // Reload device sources first so re-enabled devices are only re-announced once their provider runs again deviceProviderManager .loadFromSettings(changedSettings) - .catch((e: unknown) => logError(logger, 'Failed to reload device sources after settings change', e)) - .then(() => deviceManager.onSettingsChanged()) - .catch((e: unknown) => logError(logger, 'Failed to apply device enabled/disabled changes', e)); + .catch((e: unknown) => { logError(logger, 'Failed to reload device sources after settings change', e) }) + .then(async () => deviceManager.onSettingsChanged()) + .catch((e: unknown) => { logError(logger, 'Failed to apply device enabled/disabled changes', e) }); }); }; @@ -227,6 +235,7 @@ export const createApp = (container: Container, options: AppOptions) websocket: websocketServer, serve: (httpPort: number, sslConfig?: SslConfig): ServeResult => { const logger = container.get('logger.default'); + // eslint-disable-next-line @typescript-eslint/strict-void-return const httpServer = http.createServer(app); websocketServer.attach(httpServer); @@ -241,6 +250,7 @@ export const createApp = (container: Container, options: AppOptions) try { const key = fs.readFileSync(sslConfig.keyFile); const cert = fs.readFileSync(sslConfig.certFile); + // eslint-disable-next-line @typescript-eslint/strict-void-return const httpsServer = https.createServer({ key, cert }, app); websocketServer.attach(httpsServer); diff --git a/src/automation/scriptRuntime.ts b/src/automation/scriptRuntime.ts index 5ba65e02..1abc2b54 100644 --- a/src/automation/scriptRuntime.ts +++ b/src/automation/scriptRuntime.ts @@ -1,12 +1,14 @@ -import { AnyDevice, AnyDeviceNotification } from '../device/device.js'; -import fs, { WriteStream } from 'fs'; +import type { AnyDevice, AnyDeviceNotification } from '../device/device.js'; +import type { WriteStream } from 'fs'; +import fs from 'fs'; import readLastLines from 'read-last-lines'; -import EventEmitter from 'events'; +import type EventEmitter from 'events'; import AutomationEventType from './automationEventType.js'; -import { DeviceManagerEvent } from '../device/deviceManager.js'; -import Logger from '../logging/Logger.js'; -import ScriptVmFactory, { deviceToBridgeJson } from './scriptVmFactory.js'; -import ScriptVm from './scriptVm.js'; +import type { DeviceManagerEvent } from '../device/deviceManager.js'; +import type Logger from '../logging/Logger.js'; +import type ScriptVmFactory from './scriptVmFactory.js'; +import { deviceToBridgeJson } from './scriptVmFactory.js'; +import type ScriptVm from './scriptVm.js'; export type SupportedDeviceEvent = | { type: DeviceManagerEvent.deviceConnected | DeviceManagerEvent.deviceDisconnected | DeviceManagerEvent.deviceRefreshed, device: AnyDevice, args: [] } @@ -139,7 +141,7 @@ export default class ScriptRuntime return; } - this.eventQueue.push(() => { + this.eventQueue.push(async () => { const vm = this.vm; if (!this.acceptingEvents || vm === null) { return Promise.resolve(); @@ -151,28 +153,6 @@ export default class ScriptRuntime this.processQueuePromise ??= this.processQueue(); } - private async processQueue(): Promise - { - while (this.eventQueue.length > 0) { - const task = this.eventQueue.shift(); - - if (undefined === task) { - continue; - } - - try { - await task(); - } catch (e: unknown) { - const msg = e instanceof Error ? e.message : String(e); - this.logger.error(`VM error: ${msg}`); - this.log(msg); - this.eventEmitter.emit(AutomationEventType.consoleLog, msg); - } - } - - this.processQueuePromise = null; - } - public async getLog(maxLines: number): Promise { try { @@ -198,6 +178,40 @@ export default class ScriptRuntime return this.runningSince; } + public on(event: E, listener: ScriptRuntimeEvents[E]): this + { + this.eventEmitter.on(event, listener); + return this; + } + + public off(event: E, listener: ScriptRuntimeEvents[E]): this + { + this.eventEmitter.off(event, listener); + return this; + } + + private async processQueue(): Promise + { + while (this.eventQueue.length > 0) { + const task = this.eventQueue.shift(); + + if (undefined === task) { + continue; + } + + try { + await task(); + } catch (e: unknown) { + const msg = e instanceof Error ? e.message : String(e); + this.logger.error(`VM error: ${msg}`); + this.log(msg); + this.eventEmitter.emit(AutomationEventType.consoleLog, msg); + } + } + + this.processQueuePromise = null; + } + private log(data: string): void { this.logWriter?.write(`${data}\n`); @@ -232,16 +246,4 @@ export default class ScriptRuntime return writer; } - - public on(event: E, listener: ScriptRuntimeEvents[E]): this - { - this.eventEmitter.on(event, listener); - return this; - } - - public off(event: E, listener: ScriptRuntimeEvents[E]): this - { - this.eventEmitter.off(event, listener); - return this; - } } diff --git a/src/automation/scriptVm.ts b/src/automation/scriptVm.ts index 1bbdf059..02c09266 100644 --- a/src/automation/scriptVm.ts +++ b/src/automation/scriptVm.ts @@ -1,6 +1,6 @@ -import ivm from 'isolated-vm'; -import { EventEmitter } from 'events'; -import Logger from '../logging/Logger.js'; +import type ivm from 'isolated-vm'; +import type { EventEmitter } from 'events'; +import type Logger from '../logging/Logger.js'; export type ScriptVmSignalEvents = { eventDone: [errMsg: string | null]; @@ -42,18 +42,18 @@ export default class ScriptVm this.logger = logger.child({ name: ScriptVm.name }); } - public start(): Promise + public async start(): Promise { return this.dispatchLifecycle(LIFECYCLE_START); } - public stop(): Promise + public async stop(): Promise { return this.dispatchLifecycle(LIFECYCLE_STOP); } /** Dispatches a device event into the script and waits for its handler(s) to complete. */ - public dispatchEvent(eventType: string, deviceJson: string, args: unknown): Promise + public async dispatchEvent(eventType: string, deviceJson: string, args: unknown): Promise { const done = this.waitFor('eventDone'); // Completion is signalled via the 'eventDone' channel above; this catch only prevents an @@ -80,7 +80,7 @@ export default class ScriptVm this.isolate.dispose(); } - private dispatchLifecycle(phase: LifecyclePhase): Promise + private async dispatchLifecycle(phase: LifecyclePhase): Promise { const done = this.waitFor('lifecycleDone'); // Completion is signalled via the 'lifecycleDone' channel above; this catch only prevents @@ -90,7 +90,7 @@ export default class ScriptVm return done; } - private waitFor(channel: keyof ScriptVmSignalEvents): Promise + private async waitFor(channel: keyof ScriptVmSignalEvents): Promise { return new Promise((resolve, reject) => { this.signals.once(channel, errMsg => { diff --git a/src/automation/scriptVmFactory.ts b/src/automation/scriptVmFactory.ts index ec951f51..13d7adae 100644 --- a/src/automation/scriptVmFactory.ts +++ b/src/automation/scriptVmFactory.ts @@ -1,11 +1,13 @@ import ivm from 'isolated-vm'; import { EventEmitter } from 'events'; import { transform } from 'sucrase'; -import DeviceRepositoryInterface from '../repository/deviceRepositoryInterface.js'; -import { AttributeValue } from '../device/attribute/deviceAttribute.js'; -import { AnyDevice } from '../device/device.js'; -import Logger from '../logging/Logger.js'; -import ScriptVm, { LIFECYCLE_START, ScriptVmSignalEvents } from './scriptVm.js'; +import type DeviceRepositoryInterface from '../repository/deviceRepositoryInterface.js'; +import type { AttributeValue } from '../device/attribute/deviceAttribute.js'; +import type { AnyDevice } from '../device/device.js'; +import type Logger from '../logging/Logger.js'; +import type { ScriptVmSignalEvents } from './scriptVm.js'; +import ScriptVm, { LIFECYCLE_START } from './scriptVm.js'; +import type { DeviceId } from '../device/deviceId.js'; type BridgeDevice = { id: string; @@ -220,7 +222,7 @@ export default class ScriptVmFactory onConsoleLog(msgStr); })); - await jail.set(VM_REF_GET_ATTRIBUTE, new ivm.Reference(async (deviceId: string, attrName: string): Promise => { + await jail.set(VM_REF_GET_ATTRIBUTE, new ivm.Reference(async (deviceId: DeviceId, attrName: string): Promise => { const dev = this.deviceRepository.getById(deviceId); if (dev === null) return null; const attr = await dev.getAttribute(attrName); @@ -228,13 +230,13 @@ export default class ScriptVmFactory return JSON.stringify({ value: attr.value ?? null, name: attr.name, label: attr.label ?? null, modifier: attr.modifier, type: attr.getType() }); })); - await jail.set(VM_REF_GET_DEVICE_JSON, new ivm.Reference((deviceId: string): string | null => { + await jail.set(VM_REF_GET_DEVICE_JSON, new ivm.Reference((deviceId: DeviceId): string | null => { const dev = this.deviceRepository.getById(deviceId); if (dev === null) return null; return deviceToBridgeJson(dev); })); - await jail.set(VM_REF_SET_ATTRIBUTE, new ivm.Reference(async (deviceId: string, attrName: string, value: AttributeValue): Promise => { + await jail.set(VM_REF_SET_ATTRIBUTE, new ivm.Reference(async (deviceId: DeviceId, attrName: string, value: AttributeValue): Promise => { const dev = this.deviceRepository.getById(deviceId); if (dev === null) throw new Error(`Device not found: ${deviceId}`); await dev.setAttribute(attrName, value); @@ -262,9 +264,9 @@ export default class ScriptVmFactory const dispatchRef: unknown = await vmContext.global.get(VM_REF_DISPATCH_EVENT, { reference: true }); const lifecycleRef: unknown = await vmContext.global.get(VM_REF_DISPATCH_LIFECYCLE, { reference: true }); - if (!this.isIvmReference(dispatchRef)) { + if (!ScriptVmFactory.isIvmReference(dispatchRef)) { throw new Error(`Expected '${VM_REF_DISPATCH_EVENT}' to be an ivm.Reference`); - } else if (!this.isIvmReference(lifecycleRef)) { + } else if (!ScriptVmFactory.isIvmReference(lifecycleRef)) { throw new Error(`Expected '${VM_REF_DISPATCH_LIFECYCLE}' to be an ivm.Reference`); } @@ -276,7 +278,7 @@ export default class ScriptVmFactory } } - private isIvmReference(obj: unknown): obj is ivm.Reference { + private static isIvmReference(obj: unknown): obj is ivm.Reference { return typeof obj === 'object' && obj !== null && 'applySync' in obj && typeof obj.applySync === 'function'; } } diff --git a/src/controller/automation/createScriptController.ts b/src/controller/automation/createScriptController.ts index 45a2f7e1..c0dfae0b 100644 --- a/src/controller/automation/createScriptController.ts +++ b/src/controller/automation/createScriptController.ts @@ -1,7 +1,8 @@ -import { Request, Response } from 'express'; -import ControllerInterface from '../controllerInterface.js'; -import AutomationScriptRepositoryInterface from '../../repository/automationScriptRepositoryInterface.js'; +import type { Request, Response } from 'express'; +import type ControllerInterface from '../controllerInterface.js'; +import type AutomationScriptRepositoryInterface from '../../repository/automationScriptRepositoryInterface.js'; import { isValidAutomationScriptFileName } from '../../automation/utils.js'; +import { StatusCodes } from 'http-status-codes'; type RequestParams = { fileName: string; @@ -24,19 +25,19 @@ export default class CreateScriptController implements ControllerInterface const matchedContentType = req.is('text/plain'); if (false === matchedContentType || null === matchedContentType) { - res.status(400).send('Content-Type header must be text/plain'); + res.status(StatusCodes.BAD_REQUEST).send('Content-Type header must be text/plain'); return; } const { fileName } = req.params; if (!isValidAutomationScriptFileName(fileName)) { - res.status(400).send(`Invalid filename: ${fileName}`); + res.status(StatusCodes.BAD_REQUEST).send(`Invalid filename: ${fileName}`); return; } this.automationScriptRepository.save(fileName, req.body); - res.header('Content-Type', 'text/plain').status(201).end(req.body); + res.header('Content-Type', 'text/plain').status(StatusCodes.CREATED).end(req.body); } } diff --git a/src/controller/automation/deleteScriptController.ts b/src/controller/automation/deleteScriptController.ts index 2835d62b..56dcff46 100644 --- a/src/controller/automation/deleteScriptController.ts +++ b/src/controller/automation/deleteScriptController.ts @@ -1,7 +1,8 @@ -import { Request, Response } from 'express'; -import ControllerInterface from '../controllerInterface.js'; -import AutomationScriptRepositoryInterface from '../../repository/automationScriptRepositoryInterface.js'; +import type { Request, Response } from 'express'; +import type ControllerInterface from '../controllerInterface.js'; +import type AutomationScriptRepositoryInterface from '../../repository/automationScriptRepositoryInterface.js'; import { isValidAutomationScriptFileName } from '../../automation/utils.js'; +import { StatusCodes } from 'http-status-codes'; type RequestParams = { fileName: string }; type DeleteScriptRequest = Request; @@ -21,12 +22,12 @@ export default class DeleteScriptController implements ControllerInterface const { fileName } = req.params; if (!isValidAutomationScriptFileName(fileName)) { - res.status(400).send(`Invalid filename: ${fileName}`); + res.status(StatusCodes.BAD_REQUEST).send(`Invalid filename: ${fileName}`); return; } this.automationScriptRepository.delete(fileName); - res.sendStatus(204); + res.sendStatus(StatusCodes.NO_CONTENT); } } diff --git a/src/controller/automation/getLogController.ts b/src/controller/automation/getLogController.ts index d13f359f..adfd4a28 100644 --- a/src/controller/automation/getLogController.ts +++ b/src/controller/automation/getLogController.ts @@ -1,10 +1,13 @@ -import { Request, Response } from 'express'; +import type { Request, Response } from 'express'; +import { StatusCodes } from 'http-status-codes'; import BaseError from 'modern-errors'; -import ControllerInterface from '../controllerInterface.js'; -import ScriptRuntime from '../../automation/scriptRuntime.js'; +import type ControllerInterface from '../controllerInterface.js'; +import type ScriptRuntime from '../../automation/scriptRuntime.js'; export default class GetLogController implements ControllerInterface { + private static readonly DEFAULT_MAX_LOG_LINES = 500; + private readonly scriptRuntime: ScriptRuntime; public constructor(scriptRuntime: ScriptRuntime) @@ -14,15 +17,15 @@ export default class GetLogController implements ControllerInterface public async execute(req: Request, res: Response): Promise { - const maxLogLines = Number(req.query.limit) || 500; + const maxLogLines = Number(req.query.limit) || GetLogController.DEFAULT_MAX_LOG_LINES; try { const lines = await this.scriptRuntime.getLog(maxLogLines); - res.header('Content-Type', 'text/plain').status(200).end(lines); + res.header('Content-Type', 'text/plain').status(StatusCodes.OK).end(lines); } catch (e: unknown) { const error = BaseError.normalize(e); - res.header('Content-Type', 'text/plain').status(500).end(error.message); + res.header('Content-Type', 'text/plain').status(StatusCodes.INTERNAL_SERVER_ERROR).end(error.message); } } } diff --git a/src/controller/automation/getScriptController.ts b/src/controller/automation/getScriptController.ts index fdb5d13d..dbb756e5 100644 --- a/src/controller/automation/getScriptController.ts +++ b/src/controller/automation/getScriptController.ts @@ -1,7 +1,8 @@ -import { Request, Response } from 'express'; -import ControllerInterface from '../controllerInterface.js'; -import AutomationScriptRepositoryInterface from '../../repository/automationScriptRepositoryInterface.js'; +import type { Request, Response } from 'express'; +import type ControllerInterface from '../controllerInterface.js'; +import type AutomationScriptRepositoryInterface from '../../repository/automationScriptRepositoryInterface.js'; import { isValidAutomationScriptFileName } from '../../automation/utils.js'; +import { StatusCodes } from 'http-status-codes'; type RequestParams = { fileName: string; @@ -24,17 +25,17 @@ export default class GetScriptController implements ControllerInterface const { fileName } = req.params; if (!isValidAutomationScriptFileName(fileName)) { - res.status(400).send(`Invalid filename: ${fileName}`); + res.status(StatusCodes.BAD_REQUEST).send(`Invalid filename: ${fileName}`); return; } const scriptContent = this.automationScriptRepository.getByName(fileName); if (null === scriptContent) { - res.sendStatus(404); + res.sendStatus(StatusCodes.NOT_FOUND); return; } - res.header('Content-Type', 'text/plain').status(200).end(scriptContent); + res.header('Content-Type', 'text/plain').status(StatusCodes.OK).end(scriptContent); } } diff --git a/src/controller/automation/getScriptsController.ts b/src/controller/automation/getScriptsController.ts index 9ac2ab27..c4f988df 100644 --- a/src/controller/automation/getScriptsController.ts +++ b/src/controller/automation/getScriptsController.ts @@ -1,9 +1,9 @@ -import { Request, Response } from 'express'; -import ControllerInterface from '../controllerInterface.js'; -import AutomationScriptRepositoryInterface from '../../repository/automationScriptRepositoryInterface.js'; -import ClassToPlainSerializer from '../../serialization/classToPlainSerializer.js'; +import type { Request, Response } from 'express'; +import type ControllerInterface from '../controllerInterface.js'; +import type AutomationScriptRepositoryInterface from '../../repository/automationScriptRepositoryInterface.js'; +import type ClassToPlainSerializer from '../../serialization/classToPlainSerializer.js'; import List from '../../entity/list.js'; -import AutomationScript from '../../entity/automationScript.js'; +import type AutomationScript from '../../entity/automationScript.js'; export default class GetScriptsController implements ControllerInterface { diff --git a/src/controller/automation/runScriptController.ts b/src/controller/automation/runScriptController.ts index fa9bc8bd..a763b167 100644 --- a/src/controller/automation/runScriptController.ts +++ b/src/controller/automation/runScriptController.ts @@ -1,6 +1,7 @@ -import { Request, Response } from 'express'; -import ControllerInterface from '../controllerInterface.js'; -import ScriptRuntime from '../../automation/scriptRuntime.js'; +import type { Request, Response } from 'express'; +import type ControllerInterface from '../controllerInterface.js'; +import type ScriptRuntime from '../../automation/scriptRuntime.js'; +import { StatusCodes } from 'http-status-codes'; type RunScriptRequest = Request; @@ -18,7 +19,7 @@ export default class RunScriptController implements ControllerInterface const matchedContentType = req.is('text/plain'); if (false === matchedContentType || null === matchedContentType) { - res.status(400).send('Content-Type header must be text/plain'); + res.status(StatusCodes.BAD_REQUEST).send('Content-Type header must be text/plain'); return; } @@ -31,6 +32,6 @@ export default class RunScriptController implements ControllerInterface runningSince: this.scriptRuntime.getRunningSince(), }; - res.status(200).json(response); + res.status(StatusCodes.OK).json(response); } } diff --git a/src/controller/automation/statusScriptController.ts b/src/controller/automation/statusScriptController.ts index 8f3be524..0bc48c86 100644 --- a/src/controller/automation/statusScriptController.ts +++ b/src/controller/automation/statusScriptController.ts @@ -1,6 +1,7 @@ -import { Request, Response } from 'express'; -import ControllerInterface from '../controllerInterface.js'; -import ScriptRuntime from '../../automation/scriptRuntime.js'; +import type { Request, Response } from 'express'; +import { StatusCodes } from 'http-status-codes'; +import type ControllerInterface from '../controllerInterface.js'; +import type ScriptRuntime from '../../automation/scriptRuntime.js'; export default class StatusScriptController implements ControllerInterface { @@ -18,6 +19,6 @@ export default class StatusScriptController implements ControllerInterface runningSince: this.scriptRuntime.getRunningSince(), }; - res.status(200).json(response); + res.status(StatusCodes.OK).json(response); } } diff --git a/src/controller/automation/stopScriptController.ts b/src/controller/automation/stopScriptController.ts index 76749b86..2f5b2455 100644 --- a/src/controller/automation/stopScriptController.ts +++ b/src/controller/automation/stopScriptController.ts @@ -1,6 +1,7 @@ -import { Request, Response } from 'express'; -import ControllerInterface from '../controllerInterface.js'; -import ScriptRuntime from '../../automation/scriptRuntime.js'; +import type { Request, Response } from 'express'; +import { StatusCodes } from 'http-status-codes'; +import type ControllerInterface from '../controllerInterface.js'; +import type ScriptRuntime from '../../automation/scriptRuntime.js'; export default class StopScriptController implements ControllerInterface { @@ -15,6 +16,6 @@ export default class StopScriptController implements ControllerInterface { await this.scriptRuntime.stop(); - res.sendStatus(200); + res.sendStatus(StatusCodes.OK); } } diff --git a/src/controller/controllerInterface.ts b/src/controller/controllerInterface.ts index 15559bf1..d630e174 100644 --- a/src/controller/controllerInterface.ts +++ b/src/controller/controllerInterface.ts @@ -1,6 +1,10 @@ -import { Request, Response } from 'express'; +import type { Request, Response } from 'express'; type ControllerInterface = { + // Method syntax: controllers narrow req's generic Request per-route (see + // GetDeviceController, PatchDeviceController, etc.); property syntax would check + // contravariantly and reject that narrowing. + // eslint-disable-next-line @typescript-eslint/method-signature-style execute(req: Request, res: Response): void | Promise; }; diff --git a/src/controller/getDeviceController.ts b/src/controller/getDeviceController.ts index 1f4d7025..427f0030 100644 --- a/src/controller/getDeviceController.ts +++ b/src/controller/getDeviceController.ts @@ -1,17 +1,18 @@ -import { Request, Response } from 'express'; -import ControllerInterface from './controllerInterface.js'; -import ClassToPlainSerializer from '../serialization/classToPlainSerializer.js'; -import ConnectedDeviceRepository from '../repository/connectedDeviceRepository.js'; +import type { Request, Response } from 'express'; +import type ControllerInterface from './controllerInterface.js'; +import { StatusCodes } from 'http-status-codes'; +import type ClassToPlainSerializer from '../serialization/classToPlainSerializer.js'; +import type ConnectedDeviceRepository from '../repository/connectedDeviceRepository.js'; import deviceDiscriminator from '../serialization/discriminator/deviceDiscriminator.js'; -import { DeviceId } from '../device/deviceId.js'; +import type { DeviceId } from '../device/deviceId.js'; type GetDeviceRequest = Request<{ deviceId: DeviceId }>; export default class GetDeviceController implements ControllerInterface { - private connectedDeviceRepository: ConnectedDeviceRepository; + private readonly connectedDeviceRepository: ConnectedDeviceRepository; - private serializer: ClassToPlainSerializer; + private readonly serializer: ClassToPlainSerializer; public constructor(connectedDeviceRepository: ConnectedDeviceRepository, serializer: ClassToPlainSerializer) { @@ -25,7 +26,7 @@ export default class GetDeviceController implements ControllerInterface const device = this.connectedDeviceRepository.getById(deviceId); if (null === device) { - res.sendStatus(404); + res.sendStatus(StatusCodes.NOT_FOUND); return; } diff --git a/src/controller/getDevicesController.ts b/src/controller/getDevicesController.ts index d17d8092..f4afcc72 100644 --- a/src/controller/getDevicesController.ts +++ b/src/controller/getDevicesController.ts @@ -1,14 +1,14 @@ -import { Request, Response } from 'express'; -import ControllerInterface from './controllerInterface.js'; -import ClassToPlainSerializer from '../serialization/classToPlainSerializer.js'; -import ConnectedDeviceRepository from '../repository/connectedDeviceRepository.js'; +import type { Request, Response } from 'express'; +import type ControllerInterface from './controllerInterface.js'; +import type ClassToPlainSerializer from '../serialization/classToPlainSerializer.js'; +import type ConnectedDeviceRepository from '../repository/connectedDeviceRepository.js'; import DeviceList from '../entity/deviceList.js'; export default class GetDevicesController implements ControllerInterface { - private connectedDeviceRepository: ConnectedDeviceRepository; + private readonly connectedDeviceRepository: ConnectedDeviceRepository; - private serializer: ClassToPlainSerializer; + private readonly serializer: ClassToPlainSerializer; public constructor(connectedDeviceRepository: ConnectedDeviceRepository, serializer: ClassToPlainSerializer) { diff --git a/src/controller/healthController.ts b/src/controller/healthController.ts index 20bdfa88..1406657c 100644 --- a/src/controller/healthController.ts +++ b/src/controller/healthController.ts @@ -1,6 +1,7 @@ -import { Request, Response } from 'express'; -import ControllerInterface from './controllerInterface.js'; -import HealthMetricsCollector from '../health/healthMetricsCollector.js'; +import type { Request, Response } from 'express'; +import type ControllerInterface from './controllerInterface.js'; +import type HealthMetricsCollector from '../health/healthMetricsCollector.js'; +import { StatusCodes } from 'http-status-codes'; export default class HealthController implements ControllerInterface { @@ -16,7 +17,7 @@ export default class HealthController implements ControllerInterface const metrics = this.healthMetricsCollector.collect(); if (metrics === null) { - res.sendStatus(204); + res.sendStatus(StatusCodes.NO_CONTENT); return; } diff --git a/src/controller/patchDeviceController.ts b/src/controller/patchDeviceController.ts index 73ef6970..0dbc1313 100644 --- a/src/controller/patchDeviceController.ts +++ b/src/controller/patchDeviceController.ts @@ -1,18 +1,19 @@ -import { Request, Response } from 'express'; +import type { Request, Response } from 'express'; import BaseError from 'modern-errors'; -import ControllerInterface from './controllerInterface.js'; -import ConnectedDeviceRepository from '../repository/connectedDeviceRepository.js'; -import DeviceUpdaterInterface from '../device/updater/deviceUpdaterInterface.js'; -import { DeviceData } from '../device/device.js'; -import { DeviceId } from '../device/deviceId.js'; +import type ControllerInterface from './controllerInterface.js'; +import type ConnectedDeviceRepository from '../repository/connectedDeviceRepository.js'; +import type DeviceUpdaterInterface from '../device/updater/deviceUpdaterInterface.js'; +import type { DeviceData } from '../device/device.js'; +import type { DeviceId } from '../device/deviceId.js'; +import { StatusCodes } from 'http-status-codes'; type PatchDeviceRequest = Request<{ deviceId: DeviceId }, unknown, DeviceData>; export default class PatchDeviceController implements ControllerInterface { - private connectedDeviceRepository: ConnectedDeviceRepository; + private readonly connectedDeviceRepository: ConnectedDeviceRepository; - private deviceUpdater: DeviceUpdaterInterface; + private readonly deviceUpdater: DeviceUpdaterInterface; public constructor(connectedDeviceRepository: ConnectedDeviceRepository, deviceUpdater: DeviceUpdaterInterface) { @@ -26,7 +27,7 @@ export default class PatchDeviceController implements ControllerInterface const device = this.connectedDeviceRepository.getById(deviceId); if (null === device) { - res.sendStatus(404); + res.sendStatus(StatusCodes.NOT_FOUND); return; } @@ -34,10 +35,10 @@ export default class PatchDeviceController implements ControllerInterface await this.deviceUpdater.update(device, req.body); } catch (e: unknown) { const error = BaseError.normalize(e); - res.status(500).send(error.message); + res.status(StatusCodes.INTERNAL_SERVER_ERROR).send(error.message); return; } - res.sendStatus(202); + res.sendStatus(StatusCodes.ACCEPTED); } } diff --git a/src/controller/settings/getSettingsController.ts b/src/controller/settings/getSettingsController.ts index 7a7100c4..536deda5 100644 --- a/src/controller/settings/getSettingsController.ts +++ b/src/controller/settings/getSettingsController.ts @@ -1,13 +1,14 @@ -import { Request, Response } from 'express'; -import ControllerInterface from '../controllerInterface.js'; -import ClassToPlainSerializer from '../../serialization/classToPlainSerializer.js'; -import SettingsManager from '../../settings/settingsManager.js'; +import type { Request, Response } from 'express'; +import type ControllerInterface from '../controllerInterface.js'; +import type ClassToPlainSerializer from '../../serialization/classToPlainSerializer.js'; +import type SettingsManager from '../../settings/settingsManager.js'; +import { JSON_INDENTION_SPACES } from '../../util/numbers.js'; export default class GetSettingsController implements ControllerInterface { - private settingsManager: SettingsManager; + private readonly settingsManager: SettingsManager; - private serializer: ClassToPlainSerializer; + private readonly serializer: ClassToPlainSerializer; public constructor(settingsManager: SettingsManager, serializer: ClassToPlainSerializer) { @@ -19,6 +20,6 @@ export default class GetSettingsController implements ControllerInterface { res.contentType('application/json').send(JSON.stringify(this.serializer.transform( this.settingsManager.load(), - ), null, 2)); + ), null, JSON_INDENTION_SPACES)); } } diff --git a/src/controller/settings/putSettingsController.ts b/src/controller/settings/putSettingsController.ts index 0dece059..9ea8728d 100644 --- a/src/controller/settings/putSettingsController.ts +++ b/src/controller/settings/putSettingsController.ts @@ -1,21 +1,23 @@ -import { Request, Response } from 'express'; -import ControllerInterface from '../controllerInterface.js'; -import SettingsManager from '../../settings/settingsManager.js'; +import type { Request, Response } from 'express'; +import { StatusCodes } from 'http-status-codes'; +import type ControllerInterface from '../controllerInterface.js'; +import type SettingsManager from '../../settings/settingsManager.js'; import Settings, { SettingsSchema } from '../../settings/settings.js'; import SchemaValidationError from '../../schemaValidation/schemaValidationError.js'; -import PlainToClassSerializer from '../../serialization/plainToClassSerializer.js'; -import ClassToPlainSerializer from '../../serialization/classToPlainSerializer.js'; -import { JsonObject } from '../../types.js'; +import type PlainToClassSerializer from '../../serialization/plainToClassSerializer.js'; +import type ClassToPlainSerializer from '../../serialization/classToPlainSerializer.js'; +import type { JsonObject } from '../../types.js'; +import { JSON_INDENTION_SPACES } from '../../util/numbers.js'; type PutSettingsRequest = Request; export default class PutSettingsController implements ControllerInterface { - private settingsManager: SettingsManager; + private readonly settingsManager: SettingsManager; - private plainToClassSerializer: PlainToClassSerializer; + private readonly plainToClassSerializer: PlainToClassSerializer; - private classToPlainSerializer: ClassToPlainSerializer; + private readonly classToPlainSerializer: ClassToPlainSerializer; public constructor( settingsManager: SettingsManager, @@ -38,7 +40,7 @@ export default class PutSettingsController implements ControllerInterface throw e; } - res.status(400).json({ + res.status(StatusCodes.BAD_REQUEST).json({ message: `Settings are not in a valid format`, errors: e.validationErrors, }); @@ -49,6 +51,6 @@ export default class PutSettingsController implements ControllerInterface res.send(JSON.stringify(this.classToPlainSerializer.transform( this.settingsManager.load(), - ), null, 2)); + ), null, JSON_INDENTION_SPACES)); } } diff --git a/src/controller/versionController.ts b/src/controller/versionController.ts index 5bac5c82..1024991c 100644 --- a/src/controller/versionController.ts +++ b/src/controller/versionController.ts @@ -1,5 +1,5 @@ -import { Request, Response } from 'express'; -import ControllerInterface from './controllerInterface.js'; +import type { Request, Response } from 'express'; +import type ControllerInterface from './controllerInterface.js'; import { APP_VERSION } from '../version.js'; export default class VersionController implements ControllerInterface diff --git a/src/device/attribute/boolDeviceAttribute.ts b/src/device/attribute/boolDeviceAttribute.ts index 7506d6a4..aa898ec7 100644 --- a/src/device/attribute/boolDeviceAttribute.ts +++ b/src/device/attribute/boolDeviceAttribute.ts @@ -1,4 +1,5 @@ -import DeviceAttribute, { DeviceAttributeModifier, NotJustUndefined, NotUndefined } from './deviceAttribute.js'; +import type { DeviceAttributeModifier, NotJustUndefined, NotUndefined } from './deviceAttribute.js'; +import DeviceAttribute from './deviceAttribute.js'; type BoolDeviceAttributeValue = NotJustUndefined; @@ -23,12 +24,13 @@ export default class BoolDeviceAttribute { + public override isValidValue(value: unknown): value is NotUndefined { return typeof value === 'boolean'; } diff --git a/src/device/attribute/deviceAttribute.ts b/src/device/attribute/deviceAttribute.ts index 2f0b581a..2ad13762 100644 --- a/src/device/attribute/deviceAttribute.ts +++ b/src/device/attribute/deviceAttribute.ts @@ -1,5 +1,5 @@ import { Exclude, Expose } from 'class-transformer'; -import { Float, Int } from '../../util/numbers.js'; +import type { Float, Int } from '../../util/numbers.js'; export type NotJustUndefined = [V] extends [undefined] ? never : V; export type NotUndefined = V extends undefined ? never : V; @@ -39,6 +39,10 @@ export default abstract class DeviceAttribute(this: abstract new (...args: never[]) => U, attr: unknown): attr is U { + return attr instanceof this; + } + public get name(): string { return this._name; } @@ -67,6 +71,7 @@ export default abstract class DeviceAttribute; - - public static isInstance(this: abstract new (...args: never[]) => U, attr: unknown): attr is U { - return attr instanceof this; - } } diff --git a/src/device/attribute/floatDeviceAttribute.ts b/src/device/attribute/floatDeviceAttribute.ts index 61b89768..619504c9 100644 --- a/src/device/attribute/floatDeviceAttribute.ts +++ b/src/device/attribute/floatDeviceAttribute.ts @@ -1,4 +1,4 @@ -import { DeviceAttributeModifier, NotJustUndefined } from './deviceAttribute.js'; +import type { DeviceAttributeModifier, NotJustUndefined } from './deviceAttribute.js'; import { Float } from '../../util/numbers.js'; import NumberDeviceAttribute from './numberDeviceAttribute.js'; @@ -44,7 +44,8 @@ export default class FloatDeviceAttribute; @@ -76,7 +76,8 @@ export default class IntRangeDeviceAttribute = { key: IKey, value: IValue }; @@ -58,12 +59,15 @@ export default class ListDeviceAttribute< public fromString(value: string): V { if (this._values.length === 0 || typeof this._values[0]?.key === 'string') { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + // TODO https://github.com/SlvCtrlPlus/slvctrlplus-server/issues/107 + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-unsafe-type-assertion return value as V; } const parsedInt = parseInt(value, 10); - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + + // TODO https://github.com/SlvCtrlPlus/slvctrlplus-server/issues/107 + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-unsafe-type-assertion return (isNaN(parsedInt) ? value : parsedInt) as V; } diff --git a/src/device/attribute/numberDeviceAttribute.ts b/src/device/attribute/numberDeviceAttribute.ts index 5e163133..8638fad9 100644 --- a/src/device/attribute/numberDeviceAttribute.ts +++ b/src/device/attribute/numberDeviceAttribute.ts @@ -1,6 +1,7 @@ -import DeviceAttribute, { DeviceAttributeModifier, NotJustUndefined, NotUndefined } from './deviceAttribute.js'; +import type { DeviceAttributeModifier, NotJustUndefined, NotUndefined } from './deviceAttribute.js'; +import DeviceAttribute from './deviceAttribute.js'; import { Expose } from 'class-transformer'; -import { Float, Int } from '../../util/numbers.js'; +import type { Float, Int } from '../../util/numbers.js'; export type NumberAttributeValue = NotJustUndefined; @@ -24,7 +25,7 @@ export default abstract class NumberDeviceAttribute { + public override isValidValue(value: unknown): value is NotUndefined { return typeof value === 'number'; } } diff --git a/src/device/attribute/strDeviceAttribute.ts b/src/device/attribute/strDeviceAttribute.ts index 70c8a7db..33a6e6a6 100644 --- a/src/device/attribute/strDeviceAttribute.ts +++ b/src/device/attribute/strDeviceAttribute.ts @@ -1,4 +1,5 @@ -import DeviceAttribute, { DeviceAttributeModifier, NotJustUndefined, NotUndefined } from './deviceAttribute.js'; +import type { DeviceAttributeModifier, NotJustUndefined, NotUndefined } from './deviceAttribute.js'; +import DeviceAttribute from './deviceAttribute.js'; type StrDeviceAttributeValue = NotJustUndefined; @@ -24,12 +25,13 @@ export default class StrDeviceAttribute { + public override isValidValue(value: unknown): value is NotUndefined { return typeof value === 'string'; } diff --git a/src/device/bleDevice.ts b/src/device/bleDevice.ts index 4ff678df..617c2cc3 100644 --- a/src/device/bleDevice.ts +++ b/src/device/bleDevice.ts @@ -1,12 +1,12 @@ -import { Peripheral } from '@stoprocent/noble'; +import type { Peripheral } from '@stoprocent/noble'; import BaseError from 'modern-errors'; -import Device, { DeviceAttributes, DeviceNotifications, NoDeviceNotifications, WithUntypedAttributes } from './device.js'; -import { AnyDeviceConfig, NoDeviceConfig } from './deviceConfig.js'; +import type { DeviceAttributes, DeviceInfo, DeviceNotifications, NoDeviceNotifications, WithUntypedAttributes } from './device.js'; +import Device from './device.js'; +import type { AnyDeviceConfig, NoDeviceConfig } from './deviceConfig.js'; import { Expose } from 'class-transformer'; -import { EventEmitter } from 'events'; -import { DeviceId } from './deviceId.js'; +import type { EventEmitter } from 'events'; import { logError } from '../util/error.js'; -import Logger from '../logging/Logger.js'; +import type Logger from '../logging/Logger.js'; import { asyncHandler, promiseWithTimeout } from '../util/async.js'; export type AnyBleDevice = WithUntypedAttributes; @@ -17,6 +17,10 @@ export default abstract class BleDevice< TConfig extends AnyDeviceConfig = NoDeviceConfig, > extends Device { + private static readonly RSSI_UPDATE_INTERVAL_MS = 5000; + private static readonly RECONNECT_TIMEOUT_MS = 3000; + private static readonly COMMAND_TIMEOUT_MS = 750; + private readonly peripheral: Peripheral; private readonly rssiInterval: NodeJS.Timeout; @@ -24,36 +28,33 @@ export default abstract class BleDevice< private closing = false; @Expose() + // eslint-disable-next-line @typescript-eslint/no-unused-private-class-members private rssi: number; protected constructor( - deviceId: DeviceId, - deviceName: string, - provider: string, + deviceInfo: DeviceInfo, peripheral: Peripheral, - connectedSince: Date, - controllable: boolean, attributes: TAttributes, config: TConfig, eventEmitter: EventEmitter, logger: Logger, ) { - super(deviceId, deviceName, provider, connectedSince, controllable, attributes, config, eventEmitter, logger); + super(deviceInfo, attributes, config, eventEmitter, logger); this.peripheral = peripheral; this.rssi = peripheral.rssi; this.rssiInterval = setInterval(asyncHandler( - async () => await this.requestRssiUpdate(), + async () => { await this.requestRssiUpdate() }, (e: unknown) => logError(this.logger, `Error during RSSI update for device ${this.deviceId}`, e), - ), 5000); + ), BleDevice.RSSI_UPDATE_INTERVAL_MS); this.reconnectHandler = asyncHandler( async () => { this.logger.info(`BLE Device ${this.deviceId} disconnected, trying to reconnect`); try { if (peripheral.state !== 'connected') { - await promiseWithTimeout(peripheral.connectAsync(), 3000, `Timed out (>3s) while reconnecting to device ${this.deviceId}`); + await promiseWithTimeout(peripheral.connectAsync(), BleDevice.RECONNECT_TIMEOUT_MS, `Timed out (>3s) while reconnecting to device ${this.deviceId}`); this.logger.info(`BLE Device ${this.deviceId} reconnected successfully`); } else { this.logger.warn(`BLE Device ${this.deviceId} is not in disconnected state, current state: ${peripheral.state}`); @@ -74,29 +75,12 @@ export default abstract class BleDevice< return this.peripheral; } - private async requestRssiUpdate(): Promise { - if (this.closing || this.peripheral.state === 'disconnected') { - return; - } - - try { - const rssi = await promiseWithTimeout(this.peripheral.updateRssiAsync(), 750, `Timed out (>750ms) while updating RSSI for device ${this.deviceId}`); - this.logger.trace(`Received RSSI update for device ${this.deviceId}: ${rssi}`); - - this.rssi = rssi; - this.updateLastRefresh(); - } catch (e: unknown) { - const error = BaseError.normalize(e); - this.logger.warn(`Error updating RSSI for device ${this.deviceId}: ${error.message}`); - } - } - protected override async doClose(): Promise { this.closing = true; if (this.peripheral.state === 'connected') { try { - await promiseWithTimeout(this.peripheral.disconnectAsync(), 750, `Timed out (>750ms) while disconnecting from device ${this.deviceId}`); + await promiseWithTimeout(this.peripheral.disconnectAsync(), BleDevice.COMMAND_TIMEOUT_MS, `Timed out (>750ms) while disconnecting from device ${this.deviceId}`); } catch (error) { if (error instanceof Error && error.message.includes('BLEManager has already been cleaned up')) { return; @@ -110,4 +94,21 @@ export default abstract class BleDevice< clearInterval(this.rssiInterval); this.peripheral.off('disconnect', this.reconnectHandler); } + + private async requestRssiUpdate(): Promise { + if (this.closing || this.peripheral.state === 'disconnected') { + return; + } + + try { + const rssi = await promiseWithTimeout(this.peripheral.updateRssiAsync(), BleDevice.COMMAND_TIMEOUT_MS, `Timed out (>750ms) while updating RSSI for device ${this.deviceId}`); + this.logger.trace(`Received RSSI update for device ${this.deviceId}: ${rssi}`); + + this.rssi = rssi; + this.updateLastRefresh(); + } catch (e: unknown) { + const error = BaseError.normalize(e); + this.logger.warn(`Error updating RSSI for device ${this.deviceId}: ${error.message}`); + } + } } diff --git a/src/device/detectedDeviceOfferQueue.ts b/src/device/detectedDeviceOfferQueue.ts index d60b427c..6005de52 100644 --- a/src/device/detectedDeviceOfferQueue.ts +++ b/src/device/detectedDeviceOfferQueue.ts @@ -1,10 +1,11 @@ -import { CancellationToken, sequentialTaskQueueEvents, SequentialTaskQueue } from '@timesplinter/sequential-task-queue'; -import { AnyDevice } from './device.js'; -import { DeviceDetectionInfo } from './deviceManager.js'; +import type { CancellationToken } from '@timesplinter/sequential-task-queue'; +import { sequentialTaskQueueEvents, SequentialTaskQueue } from '@timesplinter/sequential-task-queue'; +import type { AnyDevice } from './device.js'; +import type { DeviceDetectionInfo } from './deviceManager.js'; import DeviceOfferRejectedError from './deviceOfferRejectedError.js'; -import Logger from '../logging/Logger.js'; +import type Logger from '../logging/Logger.js'; import { logError } from '../util/error.js'; -import { DetectionId } from './deviceId.js'; +import type { DetectionId } from './deviceId.js'; export type OfferResult = | { successful: true, device: D } @@ -22,30 +23,7 @@ export default class DetectedDeviceOfferQueue this.logger = logger; } - private getOrCreateQueue(detectionId: DetectionId): SequentialTaskQueue - { - let queue = this.queues.get(detectionId); - - if (queue !== undefined) { - return queue; - } - - queue = new SequentialTaskQueue(); - - queue.on(sequentialTaskQueueEvents.drained, () => { - // A revoked (closed) queue must survive its own drain - it's kept around deliberately - // as a tombstone so a late offer can still see it and reject itself. - if (!queue.isClosed) { - this.queues.delete(detectionId); - } - }); - - this.queues.set(detectionId, queue); - - return queue; - } - - public offer(deviceDetectionInfo: DeviceDetectionInfo, deviceOffer: DeviceOffer): Promise> + public async offer(deviceDetectionInfo: DeviceDetectionInfo, deviceOffer: DeviceOffer): Promise> { const detectionId = deviceDetectionInfo.detectionId; const queue = this.getOrCreateQueue(detectionId); @@ -57,7 +35,7 @@ export default class DetectedDeviceOfferQueue }); } - const task = queue.push((cancellationToken: CancellationToken) => this.runOffer(deviceOffer, cancellationToken)); + const task = queue.push(async (cancellationToken: CancellationToken) => this.runOffer(deviceOffer, cancellationToken)); return task.then( (result: OfferResult): OfferResult => { @@ -79,6 +57,63 @@ export default class DetectedDeviceOfferQueue ); } + /** + * True if a queue currently exists for this detection id - either genuinely active/in-flight, + * or a closed tombstone left behind by revoke(). Does not distinguish between the two; + * callers that need "is a fresh announce still blocked by a past revoke" must call + * dropIfRevoked() first. + */ + public has(detectionId: DetectionId): boolean + { + return this.queues.has(detectionId); + } + + public dropIfRevoked(detectionId: DetectionId): void + { + const queue = this.queues.get(detectionId); + + if ((queue?.isClosed) ?? false) { + this.queues.delete(detectionId); + } + } + + public revoke(detectionId: DetectionId, reason: DeviceOfferRejectedError): void + { + const queue = this.getOrCreateQueue(detectionId); + + void queue.close(true, reason); + } + + public closeAll(reason: DeviceOfferRejectedError): void + { + for (const detectionId of this.queues.keys()) { + this.close(detectionId, reason); + } + } + + private getOrCreateQueue(detectionId: DetectionId): SequentialTaskQueue + { + let queue = this.queues.get(detectionId); + + if (queue !== undefined) { + return queue; + } + + queue = new SequentialTaskQueue(); + + queue.on(sequentialTaskQueueEvents.drained, () => { + // A revoked (closed) queue must survive its own drain - it's kept around deliberately + // as a tombstone so a late offer can still see it and reject itself. + if (!queue.isClosed) { + this.queues.delete(detectionId); + } + }); + + this.queues.set(detectionId, queue); + + return queue; + } + private async runOffer( deviceOffer: DeviceOffer, cancellationToken: CancellationToken, @@ -106,26 +141,6 @@ export default class DetectedDeviceOfferQueue }; } - /** - * True if a queue currently exists for this detection id - either genuinely active/in-flight, - * or a closed tombstone left behind by revoke(). Does not distinguish between the two; - * callers that need "is a fresh announce still blocked by a past revoke" must call - * dropIfRevoked() first. - */ - public has(detectionId: DetectionId): boolean - { - return this.queues.has(detectionId); - } - - public dropIfRevoked(detectionId: DetectionId): void - { - const queue = this.queues.get(detectionId); - - if ((queue?.isClosed) ?? false) { - this.queues.delete(detectionId); - } - } - private close(detectionId: DetectionId, reason: DeviceOfferRejectedError): void { const queue = this.queues.get(detectionId); @@ -136,18 +151,4 @@ export default class DetectedDeviceOfferQueue this.queues.delete(detectionId); } - - public revoke(detectionId: DetectionId, reason: DeviceOfferRejectedError): void - { - const queue = this.getOrCreateQueue(detectionId); - - void queue.close(true, reason); - } - - public closeAll(reason: DeviceOfferRejectedError): void - { - for (const detectionId of this.queues.keys()) { - this.close(detectionId, reason); - } - } } diff --git a/src/device/device.ts b/src/device/device.ts index 08350b05..32d6ed80 100644 --- a/src/device/device.ts +++ b/src/device/device.ts @@ -1,12 +1,13 @@ import { Exclude, Expose } from 'class-transformer'; import DeviceState from './deviceState.js'; -import DeviceAttribute, { AttributeValue } from './attribute/deviceAttribute.js'; -import { AnyDeviceConfig, NoDeviceConfig } from './deviceConfig.js'; -import { EventEmitter } from 'events'; +import type { AttributeValue } from './attribute/deviceAttribute.js'; +import type DeviceAttribute from './attribute/deviceAttribute.js'; +import type { AnyDeviceConfig, NoDeviceConfig } from './deviceConfig.js'; +import type { EventEmitter } from 'events'; import type { DeviceId } from './deviceId.js'; import type { JsonObject } from '../types.js'; -import { DropFirst } from '../types.js'; -import Logger from '../logging/Logger.js'; +import type { DropFirst } from '../types.js'; +import type Logger from '../logging/Logger.js'; // An attribute value can be DeviceAttribute or undefined because we want to allow Partial<> export type DeviceAttributes = Record; @@ -53,11 +54,23 @@ export type DeviceEventMap< }; export type WithUntypedAttributes = Omit & { + // Method syntax: this is the AnyDevice type-erasure boundary, and needs to structurally accept + // any concrete device's narrower generic-keyed setAttribute. Property syntax would check + // parameters contravariantly and break that (see AiroticDevice/Zc95Device/etc. assignability). + // eslint-disable-next-line @typescript-eslint/method-signature-style setAttribute(attributeName: string, value: AttributeValue): Promise; }; export type AnyDevice = WithUntypedAttributes; +export type DeviceInfo = { + deviceId: DeviceId; + deviceName: string; + provider: string; + connectedSince: Date; + controllable: boolean; +}; + @Exclude() export default abstract class Device< TAttributes extends DeviceAttributes = DeviceAttributes, @@ -97,32 +110,28 @@ export default abstract class Device< @Expose() protected readonly config: TConfig; - private eventEmitter: EventEmitter; + protected readonly logger: Logger; private closePromise?: Promise; - protected readonly logger: Logger; + private readonly eventEmitter: EventEmitter; protected constructor( - deviceId: DeviceId, - deviceName: string, - provider: string, - connectedSince: Date, - controllable: boolean, + deviceInfo: DeviceInfo, attributes: TAttributes, config: TConfig, eventEmitter: EventEmitter, logger: Logger, ) { - this.deviceId = deviceId; - this.deviceName = deviceName; - this.provider = provider; - this.connectedSince = connectedSince; - this.controllable = controllable; + this.deviceId = deviceInfo.deviceId; + this.deviceName = deviceInfo.deviceName; + this.provider = deviceInfo.provider; + this.connectedSince = deviceInfo.connectedSince; + this.controllable = deviceInfo.controllable; this.attributes = attributes; this.config = config; this.eventEmitter = eventEmitter; - this.logger = logger.child({ name: `${new.target.name}.${deviceId}` }); + this.logger = logger.child({ name: `${new.target.name}.${deviceInfo.deviceId}` }); this.state = DeviceState.ready; } @@ -142,6 +151,7 @@ export default abstract class Device< return this.controllable; } + // eslint-disable-next-line @typescript-eslint/class-methods-use-this public get getRefreshInterval(): number | undefined { return undefined; } @@ -161,25 +171,16 @@ export default abstract class Device< this.updateLastRefresh(); } - protected async doRefresh(): Promise { - // no-op - } - /** * Get attribute by key * @param key The attribute key * @returns attribute value or undefined if attribute is not found. And attribute potentially cannot be found * if the generic attribute type of this class happens to be a/wrapped in a Partial */ - public getAttribute>(key: K): Promise { + public async getAttribute>(key: K): Promise { return Promise.resolve(this.attributes[key]); } - public abstract setAttribute>( - attributeName: K, - value: AttributeValueOf - ): Promise>; - public on(event: K, listener: (...args: DeviceEventMap[K]) => void): void { this.eventEmitter.on(event, listener); @@ -197,16 +198,17 @@ export default abstract class Device< return this.closePromise; } - private async performClose(): Promise - { - try { - await this.doClose(); - } finally { - this.state = DeviceState.closed; - this.emit(DeviceEvent.deviceDisconnected); - } + public abstract setAttribute>( + attributeName: K, + value: AttributeValueOf + ): Promise>; + + // eslint-disable-next-line @typescript-eslint/class-methods-use-this + protected async doRefresh(): Promise { + // no-op } + // eslint-disable-next-line @typescript-eslint/class-methods-use-this protected async doClose(): Promise { // no-op @@ -228,4 +230,14 @@ export default abstract class Device< ): attr is DeviceAttributeOf { return typeof attr === 'object' && 'name' in attr && Object.keys(this.attributes).includes(attr.name); } + + private async performClose(): Promise + { + try { + await this.doClose(); + } finally { + this.state = DeviceState.closed; + this.emit(DeviceEvent.deviceDisconnected); + } + } } diff --git a/src/device/deviceConfig.ts b/src/device/deviceConfig.ts index 688c08f7..3ba45789 100644 --- a/src/device/deviceConfig.ts +++ b/src/device/deviceConfig.ts @@ -1,4 +1,5 @@ -import { Type, Static, TObject } from '@sinclair/typebox'; +import type { Static, TObject } from '@sinclair/typebox'; +import { Type } from '@sinclair/typebox'; export const noDeviceConfigSchema = Type.Object({}, { additionalProperties: false }); diff --git a/src/device/deviceId.ts b/src/device/deviceId.ts index 12e67ab6..65d52989 100644 --- a/src/device/deviceId.ts +++ b/src/device/deviceId.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-redeclare */ import { v5 as uuidv5 } from 'uuid'; const DEVICE_NAMESPACE = '1e0758c9-799d-40b5-b2fc-63f1e66afb76'; @@ -9,22 +10,18 @@ export type DetectionId = string & { [detectionIdSymbol]: never }; export const DeviceId = { create: (seed: string): DeviceId => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions return uuidv5(seed, DEVICE_NAMESPACE) as DeviceId; }, fromDetectionId: (detectionId: DetectionId): DeviceId => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions return detectionId as unknown as DeviceId; }, }; export const DetectionId = { create: (seed: string): DetectionId => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions return uuidv5(seed, DEVICE_NAMESPACE) as DetectionId; }, fromDeviceId: (deviceId: DeviceId): DetectionId => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions return deviceId as unknown as DetectionId; }, }; diff --git a/src/device/deviceManager.ts b/src/device/deviceManager.ts index a77b5ff0..b88d5fb3 100644 --- a/src/device/deviceManager.ts +++ b/src/device/deviceManager.ts @@ -1,14 +1,16 @@ -import { AnyDevice, AnyDeviceNotification, DeviceEvent } from './device.js'; -import EventEmitter from 'events'; +import type { AnyDevice, AnyDeviceNotification } from './device.js'; +import { DeviceEvent } from './device.js'; +import type EventEmitter from 'events'; import { SequentialTaskQueue } from '@timesplinter/sequential-task-queue'; import DeviceState from './deviceState.js'; import { setIntervalAsync } from '../util/async.js'; -import Logger from '../logging/Logger.js'; +import type Logger from '../logging/Logger.js'; import { logError } from '../util/error.js'; -import { DeviceId, DetectionId } from './deviceId.js'; -import SettingsManager from '../settings/settingsManager.js'; +import type { DeviceId, DetectionId } from './deviceId.js'; +import type SettingsManager from '../settings/settingsManager.js'; import DeviceOfferRejectedError from './deviceOfferRejectedError.js'; -import DetectedDeviceOfferQueue, { OfferResult } from './detectedDeviceOfferQueue.js'; +import type { OfferResult } from './detectedDeviceOfferQueue.js'; +import DetectedDeviceOfferQueue from './detectedDeviceOfferQueue.js'; import { normalizeError } from '../util/typeUtils.js'; export type DeviceDetectionInfo = { @@ -42,6 +44,8 @@ type ConnectedDevice = { device: AnyDevice, deviceDetectionInfo: DeviceDetection export default class DeviceManager { + private static readonly DEVICE_REFRESH_TIMEOUT_MULTIPLIER = 3; + private readonly eventEmitter: EventEmitter; private readonly logger: Logger; @@ -72,17 +76,6 @@ export default class DeviceManager return this.settingsManager.getSettings()?.getKnownDeviceById(deviceId)?.enabled ?? true; } - private isDetectedDeviceAlreadyConnected(detectionId: DetectionId): boolean - { - for (const { deviceDetectionInfo } of this.connectedDevices.values()) { - if (deviceDetectionInfo.detectionId === detectionId) { - return true; - } - } - - return false; - } - public announceDetectedDevice(deviceDetectionInfo: DeviceDetectionInfo): void { this.offerQueue.dropIfRevoked(deviceDetectionInfo.detectionId); @@ -158,58 +151,8 @@ export default class DeviceManager return result; } - private registerDevice(device: AnyDevice, deviceDetectionInfo: DeviceDetectionInfo): void - { - device.on(DeviceEvent.deviceRefreshed, d => this.eventEmitter.emit(DeviceManagerEvent.deviceRefreshed, d)); - device.on(DeviceEvent.deviceDisconnected, d => { - this.connectedDevices.delete(d.getDeviceId); - this.eventEmitter.emit(DeviceManagerEvent.deviceDisconnected, d); - }); - device.on(DeviceEvent.deviceNotification, (d, notification) => this.eventEmitter.emit(DeviceManagerEvent.deviceNotification, d, notification)); - - this.initDeviceRefresher(device); - - this.connectedDevices.set(device.getDeviceId, { device, deviceDetectionInfo }); - - this.eventEmitter.emit(DeviceManagerEvent.deviceConnected, device); - } - public async onSettingsChanged(): Promise { - await this.settingsChangeQueue.push(() => this.applySettingsChange()); - } - - private async applySettingsChange(): Promise { - for (const { device, deviceDetectionInfo } of this.connectedDevices.values()) { - if (this.isDeviceEnabled(device.getDeviceId)) { - continue; - } - - this.logger.info(`Closing device '${device.getDeviceId}' since it has been disabled`); - - const deviceReleased = device.close() - .catch((e: unknown) => logError(this.logger, `Failed to close device '${device.getDeviceId}'`, e)); - - // Registered before awaiting deviceReleased below, so re-enabling this known device is - // picked up even if its provider never stops/restarts throughout (e.g. only this one - // known device was disabled, not its whole device source) - without it, nothing else - // would ever re-announce it. - this.detectedDisabledDevices.set(deviceDetectionInfo.detectionId, { deviceDetectionInfo, canonicalId: device.getDeviceId, deviceReleased }); - - await deviceReleased; - } - - for (const [detectionId, disabledDetectedDevice] of [...this.detectedDisabledDevices]) { - if (!this.isDeviceEnabled(disabledDetectedDevice.canonicalId)) { - continue; - } - - this.detectedDisabledDevices.delete(detectionId); - - // Make sure a device rejected for being disabled has finished closing before re-announcing - await disabledDetectedDevice.deviceReleased; - - this.announceDetectedDevice(disabledDetectedDevice.deviceDetectionInfo); - } + await this.settingsChangeQueue.push(async () => this.applySettingsChange()); } public getConnectedDevices(): AnyDevice[] @@ -262,6 +205,71 @@ export default class DeviceManager } } + private isDetectedDeviceAlreadyConnected(detectionId: DetectionId): boolean + { + for (const { deviceDetectionInfo } of this.connectedDevices.values()) { + if (deviceDetectionInfo.detectionId === detectionId) { + return true; + } + } + + return false; + } + + private registerDevice(device: AnyDevice, deviceDetectionInfo: DeviceDetectionInfo): void + { + device.on(DeviceEvent.deviceRefreshed, d => { + this.eventEmitter.emit(DeviceManagerEvent.deviceRefreshed, d); + }); + device.on(DeviceEvent.deviceDisconnected, d => { + this.connectedDevices.delete(d.getDeviceId); + this.eventEmitter.emit(DeviceManagerEvent.deviceDisconnected, d); + }); + device.on(DeviceEvent.deviceNotification, (d, notification) => { + this.eventEmitter.emit(DeviceManagerEvent.deviceNotification, d, notification); + }); + + this.initDeviceRefresher(device); + + this.connectedDevices.set(device.getDeviceId, { device, deviceDetectionInfo }); + + this.eventEmitter.emit(DeviceManagerEvent.deviceConnected, device); + } + + private async applySettingsChange(): Promise { + for (const { device, deviceDetectionInfo } of this.connectedDevices.values()) { + if (this.isDeviceEnabled(device.getDeviceId)) { + continue; + } + + this.logger.info(`Closing device '${device.getDeviceId}' since it has been disabled`); + + const deviceReleased = device.close() + .catch((e: unknown) => logError(this.logger, `Failed to close device '${device.getDeviceId}'`, e)); + + // Registered before awaiting deviceReleased below, so re-enabling this known device is + // picked up even if its provider never stops/restarts throughout (e.g. only this one + // known device was disabled, not its whole device source) - without it, nothing else + // would ever re-announce it. + this.detectedDisabledDevices.set(deviceDetectionInfo.detectionId, { deviceDetectionInfo, canonicalId: device.getDeviceId, deviceReleased }); + + await deviceReleased; + } + + for (const [detectionId, disabledDetectedDevice] of [...this.detectedDisabledDevices]) { + if (!this.isDeviceEnabled(disabledDetectedDevice.canonicalId)) { + continue; + } + + this.detectedDisabledDevices.delete(detectionId); + + // Make sure a device rejected for being disabled has finished closing before re-announcing + await disabledDetectedDevice.deviceReleased; + + this.announceDetectedDevice(disabledDetectedDevice.deviceDetectionInfo); + } + } + private initDeviceRefresher(device: AnyDevice): void { this.logger.info(`Initializing refresher for device '${device.getDeviceName}' (id: ${device.getDeviceId})`); const deviceRefreshIntervalMs = device.getRefreshInterval; @@ -283,7 +291,7 @@ export default class DeviceManager const deviceRefreshInterval = setIntervalAsync(deviceRefresher, { intervalMs: deviceRefreshIntervalMs, - timeoutMs: deviceRefreshIntervalMs * 3, + timeoutMs: deviceRefreshIntervalMs * DeviceManager.DEVICE_REFRESH_TIMEOUT_MULTIPLIER, onError: (e: unknown) => logError(this.logger, `device: ${device.getDeviceId} -> refresh -> failed`, e), }); diff --git a/src/device/deviceNameGenerator.ts b/src/device/deviceNameGenerator.ts index f0533c02..34d0e343 100644 --- a/src/device/deviceNameGenerator.ts +++ b/src/device/deviceNameGenerator.ts @@ -1,4 +1,5 @@ -import { Config, uniqueNamesGenerator } from 'unique-names-generator'; +import type { Config } from 'unique-names-generator'; +import { uniqueNamesGenerator } from 'unique-names-generator'; export default class DeviceNameGenerator { diff --git a/src/device/genericDeviceUpdater.ts b/src/device/genericDeviceUpdater.ts index ef9c521f..5b583aa0 100644 --- a/src/device/genericDeviceUpdater.ts +++ b/src/device/genericDeviceUpdater.ts @@ -1,13 +1,13 @@ import AbstractDeviceUpdater from './updater/abstractDeviceUpdater.js'; -import PlainToClassSerializer from '../serialization/plainToClassSerializer.js'; -import { AnyDevice, DeviceData } from './device.js'; -import Logger from '../logging/Logger.js'; +import type PlainToClassSerializer from '../serialization/plainToClassSerializer.js'; +import type { AnyDevice, DeviceData } from './device.js'; +import type Logger from '../logging/Logger.js'; import { getTypedKeys } from '../util/objects.js'; import { logError } from '../util/error.js'; export default class GenericDeviceUpdater extends AbstractDeviceUpdater { - private logger: Logger; + private readonly logger: Logger; private readonly failedMessageCountPerDevice = new Map(); diff --git a/src/device/knownDeviceRegistry.ts b/src/device/knownDeviceRegistry.ts index 726dd4a8..a3857b66 100644 --- a/src/device/knownDeviceRegistry.ts +++ b/src/device/knownDeviceRegistry.ts @@ -1,8 +1,8 @@ -import Settings from '../settings/settings.js'; +import type Settings from '../settings/settings.js'; import KnownDevice from '../settings/knownDevice.js'; -import DeviceNameGenerator from './deviceNameGenerator.js'; -import Logger from '../logging/Logger.js'; -import { DeviceId } from './deviceId.js'; +import type DeviceNameGenerator from './deviceNameGenerator.js'; +import type Logger from '../logging/Logger.js'; +import type { DeviceId } from './deviceId.js'; export default class KnownDeviceRegistry { diff --git a/src/device/peripheralDevice.ts b/src/device/peripheralDevice.ts index 75bdc900..01c10863 100644 --- a/src/device/peripheralDevice.ts +++ b/src/device/peripheralDevice.ts @@ -1,10 +1,11 @@ -import Device, { DeviceAttributes, DeviceNotifications, NoDeviceNotifications, WithUntypedAttributes } from './device.js'; -import BidirectionalDeviceTransport from './transport/deviceBidirectionalTransport.js'; -import DeviceProtocol, { AnyDeviceProtocol, AnyMessageWithResponse } from './protocol/deviceProtocol.js'; -import { AnyDeviceConfig, NoDeviceConfig } from './deviceConfig.js'; -import EventEmitter from 'events'; -import { DeviceId } from './deviceId.js'; -import Logger from '../logging/Logger.js'; +import type { DeviceAttributes, DeviceInfo, DeviceNotifications, NoDeviceNotifications, WithUntypedAttributes } from './device.js'; +import Device from './device.js'; +import type BidirectionalDeviceTransport from './transport/deviceBidirectionalTransport.js'; +import type { AnyDeviceProtocol, AnyMessageWithResponse } from './protocol/deviceProtocol.js'; +import type DeviceProtocol from './protocol/deviceProtocol.js'; +import type { AnyDeviceConfig, NoDeviceConfig } from './deviceConfig.js'; +import type EventEmitter from 'events'; +import type Logger from '../logging/Logger.js'; import { logError } from '../util/error.js'; export type AnyPeripheralDevice = WithUntypedAttributes>; @@ -21,11 +22,7 @@ export default abstract class PeripheralDevice< protected readonly protocol: TProtocol; protected constructor( - deviceId: DeviceId, - deviceName: string, - provider: string, - connectedSince: Date, - controllable: boolean, + deviceInfo: DeviceInfo, protocol: TProtocol, transport: BidirectionalDeviceTransport, attributes: TAttributes, @@ -33,7 +30,7 @@ export default abstract class PeripheralDevice< eventEmitter: EventEmitter, logger: Logger, ) { - super(deviceId, deviceName, provider, connectedSince, controllable, attributes, config, eventEmitter, logger); + super(deviceInfo, attributes, config, eventEmitter, logger); this.protocol = protocol; this.transport = transport; diff --git a/src/device/protocol/airotic/airoticDevice.ts b/src/device/protocol/airotic/airoticDevice.ts index 2c031490..7539cd38 100644 --- a/src/device/protocol/airotic/airoticDevice.ts +++ b/src/device/protocol/airotic/airoticDevice.ts @@ -1,28 +1,31 @@ import { Exclude } from 'class-transformer'; -import EventEmitter from 'events'; -import { AttributeKeyOf, AttributeValueOf, DeviceEvent } from '../../device.js'; -import StrDeviceAttribute from '../../attribute/strDeviceAttribute.js'; -import { NoDeviceConfig } from '../../deviceConfig.js'; -import { Peripheral } from '@stoprocent/noble'; -import { DeviceId } from '../../deviceId.js'; -import Logger from '../../../logging/Logger.js'; +import type EventEmitter from 'events'; +import type { AttributeKeyOf, AttributeValueOf, DeviceInfo } from '../../device.js'; +import { DeviceEvent } from '../../device.js'; +import type StrDeviceAttribute from '../../attribute/strDeviceAttribute.js'; +import type { NoDeviceConfig } from '../../deviceConfig.js'; +import type { Peripheral } from '@stoprocent/noble'; +import type Logger from '../../../logging/Logger.js'; import BleDevice from '../../bleDevice.js'; -import MessageResponseHandler from '../messageResponseHandler.js'; +import type MessageResponseHandler from '../messageResponseHandler.js'; import AiroticProtocol from './airoticProtocol.js'; -import BoolDeviceAttribute from '../../attribute/boolDeviceAttribute.js'; -import FloatDeviceAttribute from '../../attribute/floatDeviceAttribute.js'; +import type BoolDeviceAttribute from '../../attribute/boolDeviceAttribute.js'; +import type FloatDeviceAttribute from '../../attribute/floatDeviceAttribute.js'; import { sleep } from '../../../util/async.js'; -import BleUartDeviceTransport from '../../transport/bleDeviceTransport.js'; -import { Float } from '../../../util/numbers.js'; +import type BleUartDeviceTransport from '../../transport/bleDeviceTransport.js'; +import { Float, HALF_FACTOR, MIN_AS_SECONDS, SECOND_AS_MILLISECONDS } from '../../../util/numbers.js'; import typeDetect from 'type-detect'; import { hasExactLength } from '../../../util/typeUtils.js'; +import { BYTE_MAX } from '../../../util/numbers.js'; const BREATH_WINDOW_MS = 60_000; const BREATH_TIMEOUT_MS = 20_000; const BPM_TREND_INTERVALS = 6; const BPM_TREND_THRESHOLD = 0.10; -const DEFAULT_REST_COLOR = '0,0,255'; -const DEFAULT_BREATH_IN_COLOR = '255,0,128'; +const DEFAULT_REST_COLOR = `0,0,${BYTE_MAX}`; +const DEFAULT_BREATH_IN_COLOR = `${BYTE_MAX},0,128`; +const SLEEP_BETWEEN_COMMANDS_MS = 100; +const COLOR_CHANNELS = 3; export type BpmTrend = 'up' | 'down' | 'stable'; @@ -44,6 +47,8 @@ export type AiroticDeviceNotifications = { @Exclude() export default class AiroticDevice extends BleDevice { + private static readonly BREATHS_MIN_SAMPLE_COUNT = 2; + private readonly messageResponseHandler: MessageResponseHandler; private readonly transport: BleUartDeviceTransport; @@ -53,20 +58,16 @@ export default class AiroticDevice extends BleDevice | null = null; public constructor( - deviceId: DeviceId, - deviceName: string, - provider: string, + deviceInfo: DeviceInfo, peripheral: Peripheral, transport: BleUartDeviceTransport, messageResponseHandler: MessageResponseHandler, - connectedSince: Date, - controllable: boolean, attributes: AiroticDeviceAttributes, config: NoDeviceConfig, eventEmitter: EventEmitter, logger: Logger, ) { - super(deviceId, deviceName, provider, peripheral, connectedSince, controllable, attributes, config, eventEmitter, logger); + super(deviceInfo, peripheral, attributes, config, eventEmitter, logger); this.transport = transport; this.messageResponseHandler = messageResponseHandler; @@ -74,6 +75,61 @@ export default class AiroticDevice extends BleDevice this.onReceiveTransportData(data)); } + public async setAttribute< + K extends AttributeKeyOf, + V extends AttributeValueOf, + >(attributeName: K, value: V): Promise { + if (attributeName === 'restColor' && value !== undefined && typeof value === 'string') { + const { r, g, b } = AiroticDevice.parseColor(value); + await this.messageResponseHandler.send(AiroticProtocol.createSelectRestColorMessage()); + await sleep(SLEEP_BETWEEN_COMMANDS_MS); + await this.messageResponseHandler.send(AiroticProtocol.createSetColorMessage(r, g, b)); + this.attributes.restColor.value = value; + return value; + } + + if (attributeName === 'breathInColor' && value !== undefined && typeof value === 'string') { + const { r, g, b } = AiroticDevice.parseColor(value); + await this.messageResponseHandler.send(AiroticProtocol.createSelectBreathInColorMessage()); + await sleep(SLEEP_BETWEEN_COMMANDS_MS); + await this.messageResponseHandler.send(AiroticProtocol.createSetColorMessage(r, g, b)); + this.attributes.breathInColor.value = value; + return value; + } + + if (attributeName === 'resetColors' && typeof value === 'boolean') { + if (value) { + await this.messageResponseHandler.send(AiroticProtocol.createResetColorsMessage()); + this.attributes.restColor.value = DEFAULT_REST_COLOR; + this.attributes.breathInColor.value = DEFAULT_BREATH_IN_COLOR; + this.updateLastRefresh(); + } + return value; + } + + if (attributeName === 'reboot' && typeof value === 'boolean') { + if (value) { + await this.messageResponseHandler.send(AiroticProtocol.createRebootMessage()); + await sleep(SLEEP_BETWEEN_COMMANDS_MS); + await this.close(); + } + + return value; + } + + throw new Error(`Unknown attribute '${attributeName}' or invalid value type '${typeDetect(value)}'`); + } + + public override async doClose(): Promise { + if (this.breathTimeoutHandle !== null) { + clearTimeout(this.breathTimeoutHandle); + this.breathTimeoutHandle = null; + } + + await super.doClose(); + await this.transport.close(); + } + private onReceiveTransportData(data: Buffer): void { const dataStr = data.toString('utf-8'); if ('*B' !== dataStr && '*R' !== dataStr) { @@ -131,7 +187,7 @@ export default class AiroticDevice extends BleDevice, - V extends AttributeValueOf, - >(attributeName: K, value: V): Promise { - if (attributeName === 'restColor' && value !== undefined && typeof value === 'string') { - const { r, g, b } = this.parseColor(value); - await this.messageResponseHandler.send(AiroticProtocol.createSelectRestColorMessage()); - await sleep(100); - await this.messageResponseHandler.send(AiroticProtocol.createSetColorMessage(r, g, b)); - this.attributes.restColor.value = value; - return value; - } - - if (attributeName === 'breathInColor' && value !== undefined && typeof value === 'string') { - const { r, g, b } = this.parseColor(value); - await this.messageResponseHandler.send(AiroticProtocol.createSelectBreathInColorMessage()); - await sleep(100); - await this.messageResponseHandler.send(AiroticProtocol.createSetColorMessage(r, g, b)); - this.attributes.breathInColor.value = value; - return value; - } - - if (attributeName === 'resetColors' && typeof value === 'boolean') { - if (value) { - await this.messageResponseHandler.send(AiroticProtocol.createResetColorsMessage()); - this.attributes.restColor.value = DEFAULT_REST_COLOR; - this.attributes.breathInColor.value = DEFAULT_BREATH_IN_COLOR; - this.updateLastRefresh(); - } - return value; - } - - if (attributeName === 'reboot' && typeof value === 'boolean') { - if (value) { - await this.messageResponseHandler.send(AiroticProtocol.createRebootMessage()); - await sleep(500); - await this.close(); - } - - return value; - } - - throw new Error(`Unknown attribute '${attributeName}' or invalid value type '${typeDetect(value)}'`); - } - - public override async doClose(): Promise { - if (this.breathTimeoutHandle !== null) { - clearTimeout(this.breathTimeoutHandle); - this.breathTimeoutHandle = null; - } - - await super.doClose(); - await this.transport.close(); - } - - private parseColor(value: string): { r: number, g: number, b: number } { + private static parseColor(value: string): { r: number, g: number, b: number } { const channels = value.split(','); - if (!hasExactLength(channels, 3)) { - throw new Error(`Invalid color format: expected 3 components, got ${channels.length}`); + if (!hasExactLength(channels, COLOR_CHANNELS)) { + throw new Error(`Invalid color format: expected ${COLOR_CHANNELS} components, got ${channels.length}`); } const [r, g, b] = channels.map(c => { const channelNumber = parseInt(c, 10); - if (isNaN(channelNumber) || channelNumber < 0 || channelNumber > 255) { + if (isNaN(channelNumber) || channelNumber < 0 || channelNumber > BYTE_MAX) { throw new Error(`Invalid color channel value: ${c}`); } diff --git a/src/device/protocol/airotic/airoticDeviceFactory.ts b/src/device/protocol/airotic/airoticDeviceFactory.ts index 1f9594f4..a241997e 100644 --- a/src/device/protocol/airotic/airoticDeviceFactory.ts +++ b/src/device/protocol/airotic/airoticDeviceFactory.ts @@ -1,18 +1,21 @@ -import { Peripheral } from '@stoprocent/noble'; -import DateFactory from '../../../factory/dateFactory.js'; -import EventEmitterFactory from '../../../factory/eventEmitterFactory.js'; -import Logger from '../../../logging/Logger.js'; -import KnownDeviceRegistry from '../../knownDeviceRegistry.js'; +import type { Peripheral } from '@stoprocent/noble'; +import type DateFactory from '../../../factory/dateFactory.js'; +import type EventEmitterFactory from '../../../factory/eventEmitterFactory.js'; +import type Logger from '../../../logging/Logger.js'; +import type KnownDeviceRegistry from '../../knownDeviceRegistry.js'; import { DeviceAttributeModifier } from '../../attribute/deviceAttribute.js'; import StrDeviceAttribute from '../../attribute/strDeviceAttribute.js'; import BoolDeviceAttribute from '../../attribute/boolDeviceAttribute.js'; import FloatDeviceAttribute from '../../attribute/floatDeviceAttribute.js'; -import BleUartDeviceTransport from '../../transport/bleDeviceTransport.js'; +import type BleUartDeviceTransport from '../../transport/bleDeviceTransport.js'; import { hsvByteToRgb } from '../../../util/color.js'; -import { DeviceId, DetectionId } from '../../deviceId.js'; -import AiroticDevice, { AiroticDeviceAttributes } from './airoticDevice.js'; -import AiroticProtocol from './airoticProtocol.js'; -import MessageResponseHandler from '../messageResponseHandler.js'; +import type { DetectionId } from '../../deviceId.js'; +import { DeviceId } from '../../deviceId.js'; +import type { AiroticDeviceAttributes } from './airoticDevice.js'; +import AiroticDevice from './airoticDevice.js'; +import type AiroticProtocol from './airoticProtocol.js'; +import type MessageResponseHandler from '../messageResponseHandler.js'; +import { BYTE_MAX } from '../../../util/numbers.js'; export default class AiroticDeviceFactory { @@ -52,18 +55,20 @@ export default class AiroticDeviceFactory peripheral.advertisement.localName, ); - const advertisedColors = this.parseAdvertisedColors(peripheral.advertisement.manufacturerData); - const attributes = this.getAttributes(advertisedColors); + const advertisedColors = AiroticDeviceFactory.parseAdvertisedColors(peripheral.advertisement.manufacturerData); + const attributes = AiroticDeviceFactory.getAttributes(advertisedColors); const device = new AiroticDevice( - knownDevice.id, - knownDevice.name, - provider, + { + deviceId: knownDevice.id, + deviceName: knownDevice.name, + provider: provider, + connectedSince: this.dateFactory.now(), + controllable: true, + }, peripheral, transport, messageResponseHandler, - this.dateFactory.now(), - true, attributes, {}, this.eventEmitterFactory.create(), @@ -75,7 +80,7 @@ export default class AiroticDeviceFactory return device; } - private getAttributes(advertisedColors: { restColor?: string, breathInColor?: string } | undefined): AiroticDeviceAttributes { + private static getAttributes(advertisedColors: { restColor?: string, breathInColor?: string } | undefined): AiroticDeviceAttributes { return { restColor: StrDeviceAttribute.create('restColor', 'Rest Color', DeviceAttributeModifier.readWrite, advertisedColors?.restColor), breathInColor: StrDeviceAttribute.create('breathInColor', 'Breath In Color', DeviceAttributeModifier.readWrite, advertisedColors?.breathInColor), @@ -91,22 +96,21 @@ export default class AiroticDeviceFactory * initial rest/breath-in colors can be reflected without needing a UART round-trip. * Byte layout (0-indexed): 3 = colorStart.h, 4 = colorStart.s, 5 = colorTarget.h, 6 = colorTarget.s */ - private parseAdvertisedColors(manufacturerData: Buffer | undefined): { restColor?: string, breathInColor?: string } | undefined { - if (undefined === manufacturerData || manufacturerData.length < 7) { + private static parseAdvertisedColors(manufacturerData: Buffer | undefined): { restColor?: string, breathInColor?: string } | undefined { + const minManufacutrerDataLength = 7; + + if (undefined === manufacturerData || manufacturerData.length < minManufacutrerDataLength) { return undefined; } - const colorStartH = manufacturerData[3]; - const colorStartS = manufacturerData[4]; - const colorTargetH = manufacturerData[5]; - const colorTargetS = manufacturerData[6]; + const [, , , colorStartH, colorStartS, colorTargetH, colorTargetS] = manufacturerData; if (colorStartH === undefined || colorStartS === undefined || colorTargetH === undefined || colorTargetS === undefined) { return undefined; } - const target = hsvByteToRgb(colorTargetH, colorTargetS, 255); - const start = hsvByteToRgb(colorStartH, colorStartS, 255); + const target = hsvByteToRgb(colorTargetH, colorTargetS, BYTE_MAX); + const start = hsvByteToRgb(colorStartH, colorStartS, BYTE_MAX); return { restColor: `${start.r},${start.g},${start.b}`, diff --git a/src/device/protocol/airotic/airoticDeviceProvider.ts b/src/device/protocol/airotic/airoticDeviceProvider.ts index 3a9a0f92..ad2ac3ee 100644 --- a/src/device/protocol/airotic/airoticDeviceProvider.ts +++ b/src/device/protocol/airotic/airoticDeviceProvider.ts @@ -1,14 +1,15 @@ import BaseError from 'modern-errors'; -import DeviceManager from '../../deviceManager.js'; -import AiroticDevice from './airoticDevice.js'; -import Logger from '../../../logging/Logger.js'; +import type DeviceManager from '../../deviceManager.js'; +import type AiroticDevice from './airoticDevice.js'; +import type Logger from '../../../logging/Logger.js'; import { promiseWithTimeout } from '../../../util/async.js'; -import BleObserver, { BleDeviceDetectionInfo } from '../../transport/bleObserver.js'; +import type { BleDeviceDetectionInfo } from '../../transport/bleObserver.js'; +import type BleObserver from '../../transport/bleObserver.js'; import BleUartDeviceTransport from '../../transport/bleDeviceTransport.js'; import AiroticProtocol from './airoticProtocol.js'; import MessageResponseHandler from '../messageResponseHandler.js'; import BleDeviceProvider from '../../provider/bleDeviceProvider.js'; -import AiroticDeviceFactory from './airoticDeviceFactory.js'; +import type AiroticDeviceFactory from './airoticDeviceFactory.js'; export default class AiroticDeviceProvider extends BleDeviceProvider { @@ -16,6 +17,9 @@ export default class AiroticDeviceProvider extends BleDeviceProvider): Promise { try { - const answer = await messageResponseHandler.send(AiroticProtocol.createHelloMessage(), 500); + const answer = await messageResponseHandler.send(AiroticProtocol.createHelloMessage(), AiroticDeviceProvider.HANDSHAKE_TIMEOUT_MS); this.logger.debug(`Received handshake answer: ${answer}`); return true; } catch (e: unknown) { diff --git a/src/device/protocol/airotic/airoticProtocol.ts b/src/device/protocol/airotic/airoticProtocol.ts index 37691c48..be0c9553 100644 --- a/src/device/protocol/airotic/airoticProtocol.ts +++ b/src/device/protocol/airotic/airoticProtocol.ts @@ -1,4 +1,5 @@ -import DeviceProtocol, { DecodeResult, Message, MessageWithResponse } from '../deviceProtocol.js'; +import type { DecodeResult, Message, MessageWithResponse } from '../deviceProtocol.js'; +import type DeviceProtocol from '../deviceProtocol.js'; export type AiroticProtocolMessageWithResponse = MessageWithResponse; export type AiroticProtocolMessageWithoutResponse = Message; @@ -6,24 +7,6 @@ export type AiroticProtocolMessage = AiroticProtocolMessageWithResponse | Airoti export default class AiroticProtocol implements DeviceProtocol { - public encode(message: Buffer): Buffer { - return message; - } - - public decode(data: Buffer): DecodeResult { - return { - message: data.toString('utf8'), - }; - } - - public isResponseMatchingMessage(response: string, message: AiroticProtocolMessage): boolean { - if (message.message.equals(Buffer.from('!H')) && response.startsWith('Hello I am bottle')) { - return true; - } - - return false; - } - public static createHelloMessage(): AiroticProtocolMessageWithResponse { return { message: Buffer.from('!H', 'utf8'), responseType: undefined }; } @@ -49,4 +32,22 @@ export default class AiroticProtocol implements DeviceProtocol { + return { + message: data.toString('utf8'), + }; + } + + public isResponseMatchingMessage(response: string, message: AiroticProtocolMessage): boolean { + if (message.message.equals(Buffer.from('!H')) && response.startsWith('Hello I am bottle')) { + return true; + } + + return false; + } } diff --git a/src/device/protocol/buttplugIo/buttplugIoDevice.ts b/src/device/protocol/buttplugIo/buttplugIoDevice.ts index 97654a4c..2590d1d5 100644 --- a/src/device/protocol/buttplugIo/buttplugIoDevice.ts +++ b/src/device/protocol/buttplugIo/buttplugIoDevice.ts @@ -1,15 +1,16 @@ import { Exclude, Expose } from 'class-transformer'; -import { ActuatorType, ButtplugClientDevice, SensorType } from 'buttplug'; -import Device, { AttributeKeyOf, AttributeValueOf } from '../../device.js'; +import type { ButtplugClientDevice, SensorType } from 'buttplug'; +import { ActuatorType } from 'buttplug'; +import type { AttributeKeyOf, AttributeValueOf, DeviceInfo } from '../../device.js'; +import Device from '../../device.js'; import IntRangeDeviceAttribute from '../../attribute/intRangeDeviceAttribute.js'; import BoolDeviceAttribute from '../../attribute/boolDeviceAttribute.js'; import { Int } from '../../../util/numbers.js'; import IntDeviceAttribute from '../../attribute/intDeviceAttribute.js'; import { DeviceAttributeModifier } from '../../attribute/deviceAttribute.js'; -import EventEmitter from 'events'; -import { DeviceId } from '../../deviceId.js'; +import type EventEmitter from 'events'; import { asyncHandler } from '../../../util/async.js'; -import Logger from '../../../logging/Logger.js'; +import type Logger from '../../../logging/Logger.js'; import { logError } from '../../../util/error.js'; type ButtplugActuatorTypeKey = `${ActuatorType}-${number}`; @@ -27,59 +28,39 @@ type AttributeValue = AttributeValue @Exclude() export default class ButtplugIoDevice extends Device { - private readonly buttplugClientDevice: ButtplugClientDevice; + private static readonly REFRESH_INTERVAL_FOR_SENSOR_DEVICES_MS = 100; @Expose() - private deviceModel: string; + // eslint-disable-next-line @typescript-eslint/no-unused-private-class-members + private readonly deviceModel: string; + + private readonly buttplugClientDevice: ButtplugClientDevice; private readonly deviceRemovedHandler: () => void; public constructor( - deviceId: DeviceId, - deviceName: string, + deviceInfo: DeviceInfo, deviceModel: string, - provider: string, - connectedSince: Date, buttplugClientDevice: ButtplugClientDevice, attributes: ButtplugIoDeviceAttributes, eventEmitter: EventEmitter, logger: Logger, ) { - super(deviceId, deviceName, provider, connectedSince, true, attributes, {}, eventEmitter, logger); + super(deviceInfo, attributes, {}, eventEmitter, logger); this.buttplugClientDevice = buttplugClientDevice; this.deviceModel = deviceModel; this.deviceRemovedHandler = asyncHandler( - async () => { await this.close(); }, - (e: unknown) => logError(this.logger, `Failed to close removed device '${deviceId}'`, e), + async () => { await this.close() }, + (e: unknown) => logError(this.logger, `Failed to close removed device '${deviceInfo.deviceId}'`, e), ); this.buttplugClientDevice.on('deviceremoved', this.deviceRemovedHandler); } - protected override doClose(): Promise { - this.buttplugClientDevice.off('deviceremoved', this.deviceRemovedHandler); - - return Promise.resolve(); - } - public override get getRefreshInterval(): number | undefined { const sensorCount = this.buttplugClientDevice.messageAttributes.SensorReadCmd?.length ?? 0; - return (sensorCount === 0) ? undefined : 100; - } - - protected override async doRefresh(): Promise { - for (const sensor of this.buttplugClientDevice.messageAttributes.SensorReadCmd ?? []) { - const value = await this.buttplugClientDevice.sensorRead(sensor.Index, sensor.SensorType); - const attr = this.attributes[`${sensor.SensorType}-${sensor.Index}`]; - if (undefined !== attr) { - if (undefined === value[0] || isNaN(value[0])) { - this.logger.warn(`Received invalid sensor value for sensor type '${sensor.SensorType}' and index '${sensor.Index}': ${JSON.stringify(value)}. Supposed to be a number. Ignoring this value.`); - continue; - } - attr.value = Int.from(value[0]); - } - } + return (sensorCount === 0) ? undefined : ButtplugIoDevice.REFRESH_INTERVAL_FOR_SENSOR_DEVICES_MS; } public async setAttribute< @@ -113,7 +94,7 @@ export default class ButtplugIoDevice extends Device const [actuatorType, index] = attributeName.split('-'); - if (!this.isActuatorTypeKey(actuatorType) || undefined === index) { + if (!ButtplugIoDevice.isActuatorTypeKey(actuatorType) || undefined === index) { throw new Error(`Attribute with name '${attributeName}' does not correspond to a valid actuator type key`); } @@ -128,14 +109,29 @@ export default class ButtplugIoDevice extends Device return value; } - private isActuatorTypeKey(key: string | undefined): key is ActuatorType { - if (undefined === key) { - return false; - } + public get getButtplugClientDevice(): ButtplugClientDevice + { + return this.buttplugClientDevice; + } - const actuatorValueSet: (ActuatorType | string)[] = Object.values(ActuatorType); + protected override async doClose(): Promise { + this.buttplugClientDevice.off('deviceremoved', this.deviceRemovedHandler); - return actuatorValueSet.includes(key); + return Promise.resolve(); + } + + protected override async doRefresh(): Promise { + for (const sensor of this.buttplugClientDevice.messageAttributes.SensorReadCmd ?? []) { + const value = await this.buttplugClientDevice.sensorRead(sensor.Index, sensor.SensorType); + const attr = this.attributes[`${sensor.SensorType}-${sensor.Index}`]; + if (undefined !== attr) { + if (undefined === value[0] || isNaN(value[0])) { + this.logger.warn(`Received invalid sensor value for sensor type '${sensor.SensorType}' and index '${sensor.Index}': ${JSON.stringify(value)}. Supposed to be a number. Ignoring this value.`); + continue; + } + attr.value = Int.from(value[0]); + } + } } protected async send(command: ActuatorType, index: number, value: number): Promise { @@ -146,8 +142,13 @@ export default class ButtplugIoDevice extends Device }); } - public get getButtplugClientDevice(): ButtplugClientDevice - { - return this.buttplugClientDevice; + private static isActuatorTypeKey(key: string | undefined): key is ActuatorType { + if (undefined === key) { + return false; + } + + const actuatorValueSet: (ActuatorType | string)[] = Object.values(ActuatorType); + + return actuatorValueSet.includes(key); } } diff --git a/src/device/protocol/buttplugIo/buttplugIoDeviceFactory.ts b/src/device/protocol/buttplugIo/buttplugIoDeviceFactory.ts index 612d4a8c..0ba685bd 100644 --- a/src/device/protocol/buttplugIo/buttplugIoDeviceFactory.ts +++ b/src/device/protocol/buttplugIo/buttplugIoDeviceFactory.ts @@ -1,19 +1,23 @@ -import { ButtplugClientDevice } from 'buttplug'; -import ButtplugIoDevice, { ButtplugIoDeviceAttributeKey, ButtplugIoDeviceAttributes } from './buttplugIoDevice.js'; -import KnownDeviceRegistry from '../../knownDeviceRegistry.js'; -import KnownDevice from '../../../settings/knownDevice.js'; -import Logger from '../../../logging/Logger.js'; +import type { ButtplugClientDevice } from 'buttplug'; +import type { ButtplugIoDeviceAttributeKey, ButtplugIoDeviceAttributes } from './buttplugIoDevice.js'; +import ButtplugIoDevice from './buttplugIoDevice.js'; +import type KnownDeviceRegistry from '../../knownDeviceRegistry.js'; +import type KnownDevice from '../../../settings/knownDevice.js'; +import type Logger from '../../../logging/Logger.js'; import { DeviceAttributeModifier } from '../../attribute/deviceAttribute.js'; import IntRangeDeviceAttribute from '../../attribute/intRangeDeviceAttribute.js'; import BoolDeviceAttribute from '../../attribute/boolDeviceAttribute.js'; -import DateFactory from '../../../factory/dateFactory.js'; +import type DateFactory from '../../../factory/dateFactory.js'; import { Int } from '../../../util/numbers.js'; import IntDeviceAttribute from '../../attribute/intDeviceAttribute.js'; -import EventEmitterFactory from '../../../factory/eventEmitterFactory.js'; -import { DeviceId, DetectionId } from '../../deviceId.js'; +import type EventEmitterFactory from '../../../factory/eventEmitterFactory.js'; +import type { DetectionId } from '../../deviceId.js'; +import { DeviceId } from '../../deviceId.js'; export default class ButtplugIoDeviceFactory { + private static readonly RANGE_BOUNDS_LENGTH = 2; + private readonly dateFactory: DateFactory; private readonly knownDeviceRegistry: KnownDeviceRegistry; @@ -41,11 +45,14 @@ export default class ButtplugIoDeviceFactory const deviceAttrs = ButtplugIoDeviceFactory.parseDeviceAttributes(buttplugDevice); const device = new ButtplugIoDevice( - knownDevice.id, - knownDevice.name, + { + deviceId: knownDevice.id, + deviceName: knownDevice.name, + provider, + connectedSince: this.dateFactory.now(), + controllable: true, + }, buttplugDevice.name, - provider, - this.dateFactory.now(), buttplugDevice, deviceAttrs, this.eventEmitterFactory.create(), @@ -57,13 +64,22 @@ export default class ButtplugIoDeviceFactory return device; } + private resolveKnownDevice(deviceId: DeviceId, buttplugDevice: ButtplugClientDevice, provider: string): KnownDevice { + return this.knownDeviceRegistry.resolve( + deviceId, + buttplugDevice.name, + provider, + buttplugDevice.displayName ?? buttplugDevice.name, + ); + } + private static parseDeviceAttributes(buttplugDevice: ButtplugClientDevice): ButtplugIoDeviceAttributes { const attributes: ButtplugIoDeviceAttributes = {}; for (const item of buttplugDevice.messageAttributes.ScalarCmd ?? []) { const attrName: ButtplugIoDeviceAttributeKey = `${item.ActuatorType}-${item.Index}`; - if (item.StepCount > 2) { + if (item.StepCount !== ButtplugIoDeviceFactory.RANGE_BOUNDS_LENGTH) { attributes[attrName] = IntRangeDeviceAttribute.createInitialized( attrName, item.FeatureDescriptor, @@ -86,7 +102,7 @@ export default class ButtplugIoDeviceFactory // A range is defined by two numbers, if there are more or less, let's fallback // to a normal integer attribute. Not that dramatic for a sensor after all. - if ('SensorRange' in item && Array.isArray(item.SensorRange) && item.SensorRange.length === 2) { + if ('SensorRange' in item && Array.isArray(item.SensorRange) && item.SensorRange.length === ButtplugIoDeviceFactory.RANGE_BOUNDS_LENGTH) { const lowerBound: unknown = item.SensorRange[0]; const upperBound: unknown = item.SensorRange[1]; @@ -117,13 +133,4 @@ export default class ButtplugIoDeviceFactory return attributes; } - - private resolveKnownDevice(deviceId: DeviceId, buttplugDevice: ButtplugClientDevice, provider: string): KnownDevice { - return this.knownDeviceRegistry.resolve( - deviceId, - buttplugDevice.name, - provider, - buttplugDevice.displayName ?? buttplugDevice.name, - ); - } } diff --git a/src/device/protocol/buttplugIo/buttplugIoWebsocketDeviceProvider.ts b/src/device/protocol/buttplugIo/buttplugIoWebsocketDeviceProvider.ts index 28db9126..1c49d681 100644 --- a/src/device/protocol/buttplugIo/buttplugIoWebsocketDeviceProvider.ts +++ b/src/device/protocol/buttplugIo/buttplugIoWebsocketDeviceProvider.ts @@ -1,11 +1,13 @@ -import { ButtplugClientDevice, ButtplugClient, ButtplugNodeWebsocketClientConnector } from 'buttplug'; -import ButtplugIoDevice from './buttplugIoDevice.js'; +import type { ButtplugClientDevice, ButtplugNodeWebsocketClientConnector } from 'buttplug'; +import { ButtplugClient } from 'buttplug'; +import type ButtplugIoDevice from './buttplugIoDevice.js'; import DeviceProvider from '../../provider/deviceProvider.js'; -import ButtplugIoDeviceFactory from './buttplugIoDeviceFactory.js'; -import Logger from '../../../logging/Logger.js'; +import type ButtplugIoDeviceFactory from './buttplugIoDeviceFactory.js'; +import type Logger from '../../../logging/Logger.js'; import { asyncHandler, setImmediateInterval } from '../../../util/async.js'; import SlvCtrlPlusButtplugWebsocketClientConnector from './slvCtrlPlusButtplugWebsocketClientConnector.js'; -import DeviceManager, { DeviceDetectionInfo } from '../../deviceManager.js'; +import type { DeviceDetectionInfo } from '../../deviceManager.js'; +import type DeviceManager from '../../deviceManager.js'; import { logError } from '../../../util/error.js'; import { hasProperty } from '../../../util/objects.js'; import { DeviceId, DetectionId } from '../../deviceId.js'; @@ -30,8 +32,9 @@ export default class ButtplugIoWebsocketDeviceProvider extends DeviceProvider< // How long a scan window stays open; the server scans until told to stop, so we bound it ourselves private static readonly SCAN_DURATION_MS = 30_000; - private buttplugConnector: ButtplugNodeWebsocketClientConnector; - private buttplugClient: ButtplugClient; + private readonly buttplugConnector: ButtplugNodeWebsocketClientConnector; + + private readonly buttplugClient: ButtplugClient; private readonly buttplugIoDeviceFactory: ButtplugIoDeviceFactory; @@ -62,7 +65,7 @@ export default class ButtplugIoWebsocketDeviceProvider extends DeviceProvider< this.buttplugClient = new ButtplugClient('SlvCtrlPlus'); } - protected override doStart(): Promise { + protected override async doStart(): Promise { const url = `ws://${this.websocketAddress}/buttplug`; this.buttplugClient.on('disconnect', asyncHandler( @@ -97,6 +100,20 @@ export default class ButtplugIoWebsocketDeviceProvider extends DeviceProvider< } } + protected override canHandleDeviceDetectionInfo(deviceDetectionInfo: DeviceDetectionInfo): deviceDetectionInfo is ButtplugIoDeviceDetectionInfo { + return deviceDetectionInfo.type === 'buttplugIo'; + } + + protected override async createDevice(deviceDetectionInfo: ButtplugIoDeviceDetectionInfo): Promise { + const device = this.buttplugIoDeviceFactory.create( + deviceDetectionInfo.detectionId, + deviceDetectionInfo.buttplugClientDevice, + ButtplugIoWebsocketDeviceProvider.providerName, + ); + + return Promise.resolve(device); + } + private connectClient(): void { this.connectionIntervalRef ??= setImmediateInterval( () => void this.connectToServer(), @@ -188,18 +205,4 @@ export default class ButtplugIoWebsocketDeviceProvider extends DeviceProvider< private revokePendingButtplugIoDevice(buttplugDevice: ButtplugClientDevice): void { this.deviceManager.revokeDetectedDevice(this.createDeviceDetectionInfo(buttplugDevice)); } - - protected override canHandleDeviceDetectionInfo(deviceDetectionInfo: DeviceDetectionInfo): deviceDetectionInfo is ButtplugIoDeviceDetectionInfo { - return deviceDetectionInfo.type === 'buttplugIo'; - } - - protected override createDevice(deviceDetectionInfo: ButtplugIoDeviceDetectionInfo): Promise { - const device = this.buttplugIoDeviceFactory.create( - deviceDetectionInfo.detectionId, - deviceDetectionInfo.buttplugClientDevice, - ButtplugIoWebsocketDeviceProvider.providerName, - ); - - return Promise.resolve(device); - } } diff --git a/src/device/protocol/buttplugIo/buttplugIoWebsocketDeviceProviderFactory.ts b/src/device/protocol/buttplugIo/buttplugIoWebsocketDeviceProviderFactory.ts index eeed7760..70984700 100644 --- a/src/device/protocol/buttplugIo/buttplugIoWebsocketDeviceProviderFactory.ts +++ b/src/device/protocol/buttplugIo/buttplugIoWebsocketDeviceProviderFactory.ts @@ -1,8 +1,8 @@ -import DeviceProviderFactory from '../../provider/deviceProviderFactory.js'; -import Logger from '../../../logging/Logger.js'; -import ButtplugIoDeviceFactory from './buttplugIoDeviceFactory.js'; +import type DeviceProviderFactory from '../../provider/deviceProviderFactory.js'; +import type Logger from '../../../logging/Logger.js'; +import type ButtplugIoDeviceFactory from './buttplugIoDeviceFactory.js'; import ButtplugIoWebsocketDeviceProvider from './buttplugIoWebsocketDeviceProvider.js'; -import DeviceManager from '../../deviceManager.js'; +import type DeviceManager from '../../deviceManager.js'; type ButtplugIoWebsocketDeviceProviderConfig = { address: string; diff --git a/src/device/protocol/deviceProtocol.ts b/src/device/protocol/deviceProtocol.ts index af22518a..2d7d39af 100644 --- a/src/device/protocol/deviceProtocol.ts +++ b/src/device/protocol/deviceProtocol.ts @@ -45,5 +45,7 @@ export const getErrorFromDecodeResult = (protocolError: ProtocolError, transport return new Error(`Checksum validation failed for response: ${transportResponse.toString('utf-8')}`); case 'unknown_message_type': return new Error(`Unknown message type received for response: ${transportResponse.toString('utf-8')}`); + default: + return new Error(`Unknown protocol error for response '${transportResponse.toString('utf-8')}': ${JSON.stringify(protocolError)}`); } }; diff --git a/src/device/protocol/estim2b/estim2bDevice.ts b/src/device/protocol/estim2b/estim2bDevice.ts index dca7c7b8..d4b1acf3 100644 --- a/src/device/protocol/estim2b/estim2bDevice.ts +++ b/src/device/protocol/estim2b/estim2bDevice.ts @@ -1,18 +1,19 @@ -import { AttributeKeyOf, AttributeValueOf } from '../../device.js'; +import type { AttributeKeyOf, AttributeValueOf, DeviceInfo } from '../../device.js'; import IntRangeDeviceAttribute from '../../attribute/intRangeDeviceAttribute.js'; -import EStim2bProtocol, { Estim2bCommand, EStim2bMode, EStim2bStatus } from './estim2bProtocol.js'; +import type { Estim2bCommand, EStim2bStatus } from './estim2bProtocol.js'; +import type EStim2bProtocol from './estim2bProtocol.js'; +import { EStim2bMode } from './estim2bProtocol.js'; import { Exclude, Expose } from 'class-transformer'; import { Int } from '../../../util/numbers.js'; -import BoolDeviceAttribute from '../../attribute/boolDeviceAttribute.js'; -import StrDeviceAttribute from '../../attribute/strDeviceAttribute.js'; -import ListDeviceAttribute from '../../attribute/listDeviceAttribute.js'; +import type BoolDeviceAttribute from '../../attribute/boolDeviceAttribute.js'; +import type StrDeviceAttribute from '../../attribute/strDeviceAttribute.js'; +import type ListDeviceAttribute from '../../attribute/listDeviceAttribute.js'; import { DeviceAttributeModifier, isValidAttributeValue } from '../../attribute/deviceAttribute.js'; -import DeviceBidirectionalTransport from '../../transport/deviceBidirectionalTransport.js'; +import type DeviceBidirectionalTransport from '../../transport/deviceBidirectionalTransport.js'; import PeripheralDevice from '../../peripheralDevice.js'; import { getErrorFromDecodeResult } from '../deviceProtocol.js'; -import EventEmitter from 'events'; -import Logger from '../../../logging/Logger.js'; -import { DeviceId } from '../../deviceId.js'; +import type EventEmitter from 'events'; +import type Logger from '../../../logging/Logger.js'; export type EStim2bDeviceAttributes = { mode: ListDeviceAttribute; @@ -32,15 +33,21 @@ type AttributeValue = AttributeValueOf< @Exclude() export default class EStim2bDevice extends PeripheralDevice { + private static readonly REFRESH_INTERVAL_MS = 175; + private static readonly TRANSPORT_TIMEOUT_MS = 250; + private static readonly PULSE_ATTR_MIN = 2; + private static readonly PULSE_ATTR_MAX = 100; + private static readonly BATTERY_LEVEL_MAINS_THRESHOLD = 720; + private static readonly BATTERY_LEVEL_FULL_THRESHOLD = 550; + private static readonly BATTERY_LEVEL_MEDIUM_THRESHOLD = 525; + private static readonly BATTERY_LEVEL_LOW_THRESHOLD = 500; + @Expose() + // eslint-disable-next-line @typescript-eslint/no-unused-private-class-members private readonly fwVersion: string; public constructor( - deviceId: DeviceId, - deviceName: string, - provider: string, - connectedSince: Date, - controllable: boolean, + deviceInfo: DeviceInfo, status: EStim2bStatus, protocol: EStim2bProtocol, transport: DeviceBidirectionalTransport, @@ -48,46 +55,22 @@ export default class EStim2bDevice extends PeripheralDevice 720) return 'mains'; - if (adc > 550) return 'full'; - if (adc > 525) return 'medium'; - if (adc > 500) return 'low'; + if (adc > EStim2bDevice.BATTERY_LEVEL_MAINS_THRESHOLD) return 'mains'; + if (adc > EStim2bDevice.BATTERY_LEVEL_FULL_THRESHOLD) return 'full'; + if (adc > EStim2bDevice.BATTERY_LEVEL_MEDIUM_THRESHOLD) return 'medium'; + if (adc > EStim2bDevice.BATTERY_LEVEL_LOW_THRESHOLD) return 'low'; return 'critical'; } - protected override async doRefresh(): Promise { - const status = await this.send(this.protocol.createGetStatusCommand()); - this.attributes = this.setModeBasedAttributes(status); - this.updateAttributeValues(status); - } - public override get getRefreshInterval(): number { - return 175; + return EStim2bDevice.REFRESH_INTERVAL_MS; } public async setAttribute< @@ -123,10 +106,34 @@ export default class EStim2bDevice extends PeripheralDevice { + const status = await this.send(this.protocol.createGetStatusCommand()); + this.attributes = this.setModeBasedAttributes(status); + this.updateAttributeValues(status); + } + private async send(command: Estim2bCommand): Promise { const encodedMessage = this.protocol.encode(command); - const response = await this.transport.sendAndAwaitReceive(encodedMessage, 250); + const response = await this.transport.sendAndAwaitReceive(encodedMessage, EStim2bDevice.TRANSPORT_TIMEOUT_MS); const decodedResponse = this.protocol.decode(response); if ('error' in decodedResponse) { @@ -136,76 +143,76 @@ export default class EStim2bDevice extends PeripheralDevice> & Partial>) | undefined; - switch (currenStatus.currentMode) { + switch (currentStatus.currentMode) { case EStim2bMode.pulse: newAttributes = { - pulseFrequency: this.createPulseFrequencyAttribute('Pulse Feel', currenStatus), - pulsePwm: this.createPulsePwmAttribute('Pulse PWM', currenStatus), + pulseFrequency: EStim2bDevice.createPulseFrequencyAttribute('Pulse Feel', currentStatus), + pulsePwm: EStim2bDevice.createPulsePwmAttribute('Pulse PWM', currentStatus), }; break; case EStim2bMode.bounce: newAttributes = { - pulseFrequency: this.createPulseFrequencyAttribute('Bounce Rate', currenStatus), - pulsePwm: this.createPulsePwmAttribute('Pulse Feel', currenStatus), + pulseFrequency: EStim2bDevice.createPulseFrequencyAttribute('Bounce Rate', currentStatus), + pulsePwm: EStim2bDevice.createPulsePwmAttribute('Pulse Feel', currentStatus), }; break; case EStim2bMode.continuous: newAttributes = { - pulseFrequency: this.createPulseFrequencyAttribute('Pulse Feel', currenStatus), + pulseFrequency: EStim2bDevice.createPulseFrequencyAttribute('Pulse Feel', currentStatus), }; break; case EStim2bMode.aSplit: newAttributes = { - pulseFrequency: this.createPulseFrequencyAttribute('B Pulse Rate', currenStatus), - pulsePwm: this.createPulsePwmAttribute('Pulse Feel', currenStatus), + pulseFrequency: EStim2bDevice.createPulseFrequencyAttribute('B Pulse Rate', currentStatus), + pulsePwm: EStim2bDevice.createPulsePwmAttribute('Pulse Feel', currentStatus), }; break; case EStim2bMode.bSplit: newAttributes = { - pulseFrequency: this.createPulseFrequencyAttribute('A Pulse Rate', currenStatus), - pulsePwm: this.createPulsePwmAttribute('Pulse Feel', currenStatus), + pulseFrequency: EStim2bDevice.createPulseFrequencyAttribute('A Pulse Rate', currentStatus), + pulsePwm: EStim2bDevice.createPulsePwmAttribute('Pulse Feel', currentStatus), }; break; case EStim2bMode.wave: case EStim2bMode.waterfall: newAttributes = { - pulseFrequency: this.createPulseFrequencyAttribute('Flow', currenStatus), - pulsePwm: this.createPulsePwmAttribute('Granularity', currenStatus), + pulseFrequency: EStim2bDevice.createPulseFrequencyAttribute('Flow', currentStatus), + pulsePwm: EStim2bDevice.createPulsePwmAttribute('Granularity', currentStatus), }; break; case EStim2bMode.squeeze: case EStim2bMode.milk: newAttributes = { - pulseFrequency: this.createPulseFrequencyAttribute('Pulse speed', currenStatus), - pulsePwm: this.createPulsePwmAttribute('Feel', currenStatus), + pulseFrequency: EStim2bDevice.createPulseFrequencyAttribute('Pulse speed', currentStatus), + pulsePwm: EStim2bDevice.createPulsePwmAttribute('Feel', currentStatus), }; break; case EStim2bMode.throb: case EStim2bMode.thrust: newAttributes = { - pulseFrequency: this.createPulseFrequencyAttribute('Range', currenStatus), + pulseFrequency: EStim2bDevice.createPulseFrequencyAttribute('Range', currentStatus), }; break; case EStim2bMode.random: newAttributes = { - pulseFrequency: this.createPulseFrequencyAttribute('Range', currenStatus), - pulsePwm: this.createPulsePwmAttribute('Pulse Feel', currenStatus), + pulseFrequency: EStim2bDevice.createPulseFrequencyAttribute('Range', currentStatus), + pulsePwm: EStim2bDevice.createPulsePwmAttribute('Pulse Feel', currentStatus), }; break; case EStim2bMode.step: newAttributes = { - pulseFrequency: this.createPulseFrequencyAttribute('Step Size', currenStatus), - pulsePwm: this.createPulsePwmAttribute('Pulse Feel', currenStatus), + pulseFrequency: EStim2bDevice.createPulseFrequencyAttribute('Step Size', currentStatus), + pulsePwm: EStim2bDevice.createPulsePwmAttribute('Pulse Feel', currentStatus), }; break; case EStim2bMode.training: newAttributes = { - pulseFrequency: this.createPulseFrequencyAttribute('Jump Size', currenStatus), - pulsePwm: this.createPulsePwmAttribute('Pulse Feel', currenStatus), + pulseFrequency: EStim2bDevice.createPulseFrequencyAttribute('Jump Size', currentStatus), + pulsePwm: EStim2bDevice.createPulsePwmAttribute('Pulse Feel', currentStatus), }; break; default: @@ -227,27 +234,27 @@ export default class EStim2bDevice extends PeripheralDevice !isNaN(Number(key))) .map(([key, value]) => ({ key: Int.from(parseInt(key, 10)), value: Estim2bDeviceFactory.formatMode(value) })) @@ -81,8 +90,8 @@ export default class Estim2bDeviceFactory 'Channel A', DeviceAttributeModifier.readWrite, undefined, - Int.ZERO, - Int.from(100), + Int.from(Estim2bDeviceFactory.CHANNEL_POWER_MIN), + Int.from(Estim2bDeviceFactory.CHANNEL_POWER_MAX), Int.from(1), Int.from(initialStatus.channelALevel), ); @@ -92,8 +101,8 @@ export default class Estim2bDeviceFactory 'Channel B', DeviceAttributeModifier.readWrite, undefined, - Int.ZERO, - Int.from(100), + Int.from(Estim2bDeviceFactory.CHANNEL_POWER_MIN), + Int.from(Estim2bDeviceFactory.CHANNEL_POWER_MAX), Int.from(1), Int.from(initialStatus.channelBLevel), ); diff --git a/src/device/protocol/estim2b/estim2bProtocol.ts b/src/device/protocol/estim2b/estim2bProtocol.ts index 492fa5a5..1c71206f 100644 --- a/src/device/protocol/estim2b/estim2bProtocol.ts +++ b/src/device/protocol/estim2b/estim2bProtocol.ts @@ -1,5 +1,6 @@ import { hasExactLength } from '../../../util/typeUtils.js'; -import DeviceProtocol, { DecodeResult, InferMessage, MessageWithResponse } from '../deviceProtocol.js'; +import type { DecodeResult, InferMessage, MessageWithResponse } from '../deviceProtocol.js'; +import type DeviceProtocol from '../deviceProtocol.js'; export type EStim2bStatus = { batteryLevel: number; @@ -58,7 +59,23 @@ export type EStim2bProtocolMessage = MessageWithResponse { - private static isEStim2PowerMode = (value: string): value is EStim2PowerMode => 'H' === value || 'L' === value; + // Commands 'J' (join channels) and 'U' (unlink channels) are documented across the internet, + // but they don't really exist. The official Commander3 app also doesn't allow joining/unlinking the channels. + private static readonly commandRequestStatus = ''; + private static readonly commandSetPulseFrequency = 'C'; + private static readonly commandSetPulsePwm = 'D'; + private static readonly commandSetMode = 'M'; + private static readonly commandSetPowerZero = 'K'; + private static readonly commandReset = 'E'; + private static readonly deviceInternalPrecisionFactor = 2; + private static readonly minModeIndex = 0; + private static readonly maxModeIndex = 16; + private static readonly minPulseValue = 2; + private static readonly maxPulseValue = 100; + private static readonly minPowerValue = 0; + private static readonly maxPowerValue = 99; + + private static readonly isEStim2PowerMode = (value: string): value is EStim2PowerMode => 'H' === value || 'L' === value; public encode(message: InferMessage): Buffer { return Buffer.from(message, 'utf-8'); @@ -72,15 +89,6 @@ export default class EStim2bProtocol implements DeviceProtocol { const parts = response.split(':'); + const expectedDataSegments = 9; - if (!hasExactLength(parts, 9)) { + if (!hasExactLength(parts, expectedDataSegments)) { return { error: { type: 'invalid_frame', - reason: `Expected 9 parts, got ${parts.length} (${response})`, + reason: `Expected ${expectedDataSegments} parts, got ${parts.length} (${response})`, }, }; } @@ -201,10 +210,10 @@ export default class EStim2bProtocol implements DeviceProtocol { @@ -61,7 +63,7 @@ export default class EStim2bSerialDeviceProvider extends SerialDeviceProvider = DeviceProtocol> & P export default class MessageResponseHandler

{ + private static readonly TIMEOUT_PERCENTAGE_FOR_SLOW_RESPONSE_LOG = 0.8; + private readonly protocol: TypedProtocol

; private readonly transport: DeviceBidirectionalTransport; private readonly logger: Logger; private readonly pendingEntries = new Set>>(); private readonly timeoutMs: number; - public static create

( - protocol: TypedProtocol

, - transport: DeviceBidirectionalTransport, - logger: Logger, - timeoutMs = 200, - ): MessageResponseHandler

{ - return new this(protocol, transport, logger, timeoutMs); - } - private constructor( protocol: TypedProtocol

, transport: DeviceBidirectionalTransport, @@ -57,54 +51,33 @@ export default class MessageResponseHandler

}); } - private onResponse(data: Buffer): void { - const decodedMessage = this.protocol.decode(data); - - if ('error' in decodedMessage) { - this.logger.error(`Could not decode message`, decodedMessage.error); - return; - } - - const message = decodedMessage.message; - - for (const entry of this.pendingEntries) { - if (this.protocol.isResponseMatchingMessage(message, entry.msg)) { - clearTimeout(entry.timeout); - this.pendingEntries.delete(entry); - entry.resolve(message); - const responseTime = Date.now() - entry.pendingSince; - - if (responseTime > entry.timeoutMs * 0.8) { - this.logger.warn( - `Slow response time (${responseTime}ms) for message: ${JSON.stringify(entry.msg.message)}`, - ); - } - - break; - } - } - } - - private isMessageWithResponse>( - msg: T, - ): msg is Extract { - return 'responseType' in msg; + public static create

( + protocol: TypedProtocol

, + transport: DeviceBidirectionalTransport, + logger: Logger, + timeoutMs = 200, + ): MessageResponseHandler

{ + return new this(protocol, transport, logger, timeoutMs); } - public async send>( + public async send, AnyMessageWithResponse>>( msg: MR, timeoutMs?: number, - ): Promise> { + ): Promise>; + public async send( + msg: Exclude, AnyMessageWithResponse>, + timeoutMs?: number, + ): Promise; + public async send>(msg: InferMR

, timeoutMs?: number): Promise | void> { const encodedMsg = this.protocol.encode(msg.message); const realTimeoutMs = timeoutMs ?? this.timeoutMs; if (!this.isMessageWithResponse(msg)) { - return promiseWithTimeout(new Promise>((resolve, reject) => { - this.transport.send(encodedMsg).then(() => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions - resolve(undefined as InferResponse); - }).catch(reject); - }), realTimeoutMs, `Message ${encodedMsg.toString()} timed out after ${realTimeoutMs}ms`); + return promiseWithTimeout( + this.transport.send(encodedMsg), + realTimeoutMs, + `Message ${encodedMsg.toString()} timed out after ${realTimeoutMs}ms`, + ); } return new Promise>((resolve, reject) => { @@ -131,4 +104,40 @@ export default class MessageResponseHandler

}); }); } + + // Static members cannot reference class type parameters.ts(2302): + // eslint-disable-next-line @typescript-eslint/class-methods-use-this + private isMessageWithResponse>( + msg: T, + ): msg is Extract { + return 'responseType' in msg; + } + + private onResponse(data: Buffer): void { + const decodedMessage = this.protocol.decode(data); + + if ('error' in decodedMessage) { + this.logger.error(`Could not decode message`, decodedMessage.error); + return; + } + + const message = decodedMessage.message; + + for (const entry of this.pendingEntries) { + if (this.protocol.isResponseMatchingMessage(message, entry.msg)) { + clearTimeout(entry.timeout); + this.pendingEntries.delete(entry); + entry.resolve(message); + const responseTime = Date.now() - entry.pendingSince; + + if (responseTime > entry.timeoutMs * MessageResponseHandler.TIMEOUT_PERCENTAGE_FOR_SLOW_RESPONSE_LOG) { + this.logger.warn( + `Slow response time (${responseTime}ms) for message: ${JSON.stringify(entry.msg.message)}`, + ); + } + + break; + } + } + } } diff --git a/src/device/protocol/slvCtrlPlus/genericSlvCtrlPlusDevice.ts b/src/device/protocol/slvCtrlPlus/genericSlvCtrlPlusDevice.ts index 94c4bb31..d488b390 100644 --- a/src/device/protocol/slvCtrlPlus/genericSlvCtrlPlusDevice.ts +++ b/src/device/protocol/slvCtrlPlus/genericSlvCtrlPlusDevice.ts @@ -1,12 +1,12 @@ import { Exclude, Expose } from 'class-transformer'; -import SlvCtrlPlusDevice, { SlvCtrlPlusDeviceAttributes } from './slvCtrlPlusDevice.js'; +import type { SlvCtrlPlusDeviceAttributes } from './slvCtrlPlusDevice.js'; +import SlvCtrlPlusDevice from './slvCtrlPlusDevice.js'; import DeviceState from '../../deviceState.js'; -import { AttributeKeyOf, AttributeValueOf } from '../../device.js'; -import SlvCtrlProtocol from './slvCtrlProtocol.js'; -import DeviceBidirectionalTransport from '../../transport/deviceBidirectionalTransport.js'; -import EventEmitter from 'events'; -import Logger from '../../../logging/Logger.js'; -import { DeviceId } from '../../deviceId.js'; +import type { AttributeKeyOf, AttributeValueOf, DeviceInfo } from '../../device.js'; +import type SlvCtrlProtocol from './slvCtrlProtocol.js'; +import type DeviceBidirectionalTransport from '../../transport/deviceBidirectionalTransport.js'; +import type EventEmitter from 'events'; +import type Logger from '../../../logging/Logger.js'; type AttributeValue = AttributeValueOf; @@ -19,18 +19,17 @@ export default class GenericSlvCtrlPlusDevice extends SlvCtrlPlusDevice private readonly deviceModel: string; @Expose() + // eslint-disable-next-line @typescript-eslint/no-unused-private-class-members private readonly fwVersion: number; @Expose() + // eslint-disable-next-line @typescript-eslint/no-unused-private-class-members private readonly protocolVersion: number; public constructor( + deviceInfo: DeviceInfo, fwVersion: number, - deviceId: DeviceId, - deviceName: string, deviceModel: string, - provider: string, - connectedSince: Date, protocol: SlvCtrlProtocol, transport: DeviceBidirectionalTransport, protocolVersion: number, @@ -38,32 +37,13 @@ export default class GenericSlvCtrlPlusDevice extends SlvCtrlPlusDevice eventEmitter: EventEmitter, logger: Logger, ) { - super(deviceId, deviceName, provider, connectedSince, protocol, transport, false, attributes, {}, eventEmitter, logger); + super(deviceInfo, protocol, transport, attributes, {}, eventEmitter, logger); this.deviceModel = deviceModel; this.fwVersion = fwVersion; this.protocolVersion = protocolVersion; } - protected override async doRefresh(): Promise { - const response = await this.send({ command: 'status', args: [] }); - - for (const attrKey in response.data) { - if (!(attrKey in this.attributes)) { - continue; - } - - const attribute = this.attributes[attrKey]; - - // Ignore attributes that were not announced by the device during handshake - if (undefined === attribute) { - continue; - } - - attribute.value = (undefined !== response.data[attrKey] && '' !== response.data[attrKey]) ? attribute.fromString(response.data[attrKey]) : undefined; - } - } - public async setAttribute< K extends AttributeKeyOf, >(attributeName: K, value: AttributeValue): Promise> { @@ -103,11 +83,30 @@ export default class GenericSlvCtrlPlusDevice extends SlvCtrlPlusDevice return this.deviceModel; } + public override get getRefreshInterval(): number { + return 100; + } + protected getSerialTimeout(): number { return this.serialTimeout; } - public override get getRefreshInterval(): number { - return 100; + protected override async doRefresh(): Promise { + const response = await this.send({ command: 'status', args: [] }); + + for (const attrKey in response.data) { + if (!(attrKey in this.attributes)) { + continue; + } + + const attribute = this.attributes[attrKey]; + + // Ignore attributes that were not announced by the device during handshake + if (undefined === attribute) { + continue; + } + + attribute.value = (undefined !== response.data[attrKey] && '' !== response.data[attrKey]) ? attribute.fromString(response.data[attrKey]) : undefined; + } } } diff --git a/src/device/protocol/slvCtrlPlus/slvCtrlPlusDevice.ts b/src/device/protocol/slvCtrlPlus/slvCtrlPlusDevice.ts index b2bd85a8..de10cc00 100644 --- a/src/device/protocol/slvCtrlPlus/slvCtrlPlusDevice.ts +++ b/src/device/protocol/slvCtrlPlus/slvCtrlPlusDevice.ts @@ -1,14 +1,14 @@ import { Exclude } from 'class-transformer'; -import DeviceAttribute from '../../attribute/deviceAttribute.js'; -import { AnyDeviceConfig, NoDeviceConfig } from '../../deviceConfig.js'; -import { DeviceNotifications, NoDeviceNotifications } from '../../device.js'; -import SlvCtrlProtocol, { SlvCtrlProtocolCommand, SlvCtrlProtocolResponse } from './slvCtrlProtocol.js'; -import DeviceBidirectionalTransport from '../../transport/deviceBidirectionalTransport.js'; +import type DeviceAttribute from '../../attribute/deviceAttribute.js'; +import type { AnyDeviceConfig, NoDeviceConfig } from '../../deviceConfig.js'; +import type { DeviceInfo, DeviceNotifications, NoDeviceNotifications } from '../../device.js'; +import type { SlvCtrlProtocolCommand, SlvCtrlProtocolResponse } from './slvCtrlProtocol.js'; +import SlvCtrlProtocol from './slvCtrlProtocol.js'; +import type DeviceBidirectionalTransport from '../../transport/deviceBidirectionalTransport.js'; import PeripheralDevice from '../../peripheralDevice.js'; import { getErrorFromDecodeResult } from '../deviceProtocol.js'; -import EventEmitter from 'events'; -import Logger from '../../../logging/Logger.js'; -import { DeviceId } from '../../deviceId.js'; +import type EventEmitter from 'events'; +import type Logger from '../../../logging/Logger.js'; type SlvCtrlPlusDeviceAttributeKey = string; @@ -21,19 +21,15 @@ export default abstract class SlvCtrlPlusDevice< TConfig extends AnyDeviceConfig = NoDeviceConfig, > extends PeripheralDevice { protected constructor( - deviceId: DeviceId, - deviceName: string, - provider: string, - connectedSince: Date, + deviceInfo: DeviceInfo, protocol: SlvCtrlProtocol, transport: DeviceBidirectionalTransport, - controllable: boolean, attributes: TAttributes, config: TConfig, eventEmitter: EventEmitter, logger: Logger, ) { - super(deviceId, deviceName, provider, connectedSince, controllable, protocol, transport, attributes, config, eventEmitter, logger); + super(deviceInfo, protocol, transport, attributes, config, eventEmitter, logger); } protected async send(command: SlvCtrlProtocolCommand): Promise diff --git a/src/device/protocol/slvCtrlPlus/slvCtrlPlusDeviceFactory.ts b/src/device/protocol/slvCtrlPlus/slvCtrlPlusDeviceFactory.ts index ef16324b..1e8906d6 100644 --- a/src/device/protocol/slvCtrlPlus/slvCtrlPlusDeviceFactory.ts +++ b/src/device/protocol/slvCtrlPlus/slvCtrlPlusDeviceFactory.ts @@ -1,22 +1,24 @@ -import KnownDeviceRegistry from '../../knownDeviceRegistry.js'; +import type KnownDeviceRegistry from '../../knownDeviceRegistry.js'; import GenericSlvCtrlPlusDevice from './genericSlvCtrlPlusDevice.js'; -import DateFactory from '../../../factory/dateFactory.js'; -import DeviceBidirectionalTransport from '../../transport/deviceBidirectionalTransport.js'; +import type DateFactory from '../../../factory/dateFactory.js'; +import type DeviceBidirectionalTransport from '../../transport/deviceBidirectionalTransport.js'; import SlvCtrlProtocolLegacy from './slvCtrlProtocolLegacy.js'; -import Logger from '../../../logging/Logger.js'; +import type Logger from '../../../logging/Logger.js'; import SlvCtrlProtocolV1 from './slvCtrlProtocolV1.js'; -import SlvCtrlProtocol, { DeviceInfo } from './slvCtrlProtocol.js'; +import type { DeviceInfo } from './slvCtrlProtocol.js'; +import SlvCtrlProtocol from './slvCtrlProtocol.js'; import { getErrorFromDecodeResult } from '../deviceProtocol.js'; -import EventEmitterFactory from '../../../factory/eventEmitterFactory.js'; -import { SlvCtrlPlusDeviceAttributes } from './slvCtrlPlusDevice.js'; -import { DeviceId, DetectionId } from '../../deviceId.js'; +import type EventEmitterFactory from '../../../factory/eventEmitterFactory.js'; +import type { SlvCtrlPlusDeviceAttributes } from './slvCtrlPlusDevice.js'; +import type { DetectionId } from '../../deviceId.js'; +import { DeviceId } from '../../deviceId.js'; export default class SlvCtrlPlusDeviceFactory { - private readonly dateFactory: DateFactory; - protected readonly eventEmitterFactory: EventEmitterFactory; + private readonly dateFactory: DateFactory; + private readonly knownDeviceRegistry: KnownDeviceRegistry; private readonly logger: Logger; @@ -38,15 +40,18 @@ export default class SlvCtrlPlusDeviceFactory const protocol = deviceInfo.protocol; const knownDevice = this.knownDeviceRegistry.resolve(DeviceId.fromDetectionId(detectionId), deviceInfo.deviceType, provider); - const deviceAttributes = await this.getAttributes(transport, protocol); + const deviceAttributes = await SlvCtrlPlusDeviceFactory.getAttributes(transport, protocol); const device = new GenericSlvCtrlPlusDevice( + { + deviceId: knownDevice.id, + deviceName: knownDevice.name, + provider: provider, + connectedSince: this.dateFactory.now(), + controllable: true, + }, deviceInfo.fwVersion, - knownDevice.id, - knownDevice.name, deviceInfo.deviceType, - provider, - this.dateFactory.now(), protocol, transport, deviceInfo.protocolVersion, @@ -97,7 +102,16 @@ export default class SlvCtrlPlusDeviceFactory return { fwVersion, protocolVersion, deviceType, protocol }; } - private async getAttributes(transport: DeviceBidirectionalTransport, protocol: SlvCtrlProtocol): Promise + private getProtocol(introductionResult: string): SlvCtrlProtocol { + if (/^introduce;([^,;]+),(\d+),(\d+)$/.test(introductionResult)) { + this.logger.info('SlvCtrl protocol { const attrResponse = await transport.sendAndAwaitReceive( protocol.encode({ command: 'attributes', args: [] }), @@ -116,13 +130,4 @@ export default class SlvCtrlPlusDeviceFactory return protocol.getAttributes(decodedAttrResponse.message.data); } - - private getProtocol(introductionResult: string): SlvCtrlProtocol { - if (/^introduce;([^,;]+),(\d+),(\d+)$/.test(introductionResult)) { - this.logger.info('SlvCtrl protocol { public static readonly providerName = 'slvCtrlPlusSerial'; - private static readonly moduleReadyByte = 0x07; + private static readonly SERIAL_READY_BYTE = 0x07; + private static readonly SERIAL_READY_BYTE_TIMEOUT_MS = 3000; + private static readonly HANDSHAKE_TIMEOUT_MS = 250; + private static readonly HANDSHAKE_MAX_RETRIES = 4; private static readonly arduinoVendorId = '2341'; @@ -45,7 +50,7 @@ export default class SlvCtrlPlusSerialDeviceProvider extends SerialDeviceProvide const syncPort = new SynchronousSerialPort(deviceDetectionInfo.portInfo, parser, port, this.logger); const transport = this.deviceTransportFactory.create(syncPort, undefined, Buffer.from(SlvCtrlProtocol.EOF)); - await this.performHandshakeWithRetries(transport, 4); + await this.performHandshakeWithRetries(transport, SlvCtrlPlusSerialDeviceProvider.HANDSHAKE_MAX_RETRIES); const device = await this.slvCtrlPlusDeviceFactory.create( deviceDetectionInfo.detectionId, @@ -58,28 +63,11 @@ export default class SlvCtrlPlusSerialDeviceProvider extends SerialDeviceProvide return device; } - private async performHandshakeWithRetries(transport: DeviceBidirectionalTransport, maxAttempts: number): Promise { - let lastError; - - for (let i = 1; i <= maxAttempts; i++) { - try { - await transport.sendAndAwaitReceive(Buffer.from(`clear`), 250); - return; - } catch (e: unknown) { - const error = BaseError.normalize(e); - this.logger.info(`Retrying because handshake attempt ${i} failed: ${error.message}`); - if (i === maxAttempts) lastError = e; - } - } - - throw lastError; - } - - protected getSerialDeviceProviderPortOpenOptions(): SerialDeviceProviderPortOpenOptions { + protected override getSerialDeviceProviderPortOpenOptions(): SerialDeviceProviderPortOpenOptions { return { baudRate: 9600 }; } - protected override preparePort(port: SerialPortStream, portInfo: PortInfo): Promise { + protected override async preparePort(port: SerialPortStream, portInfo: PortInfo): Promise { return new Promise((resolve, reject) => { if (portInfo.vendorId !== SlvCtrlPlusSerialDeviceProvider.arduinoVendorId) { // It's NOT an Arduino @@ -88,7 +76,7 @@ export default class SlvCtrlPlusSerialDeviceProvider extends SerialDeviceProvide } const readyParser = port.pipe(new ReadyParser({ - delimiter: [SlvCtrlPlusSerialDeviceProvider.moduleReadyByte], + delimiter: [SlvCtrlPlusSerialDeviceProvider.SERIAL_READY_BYTE], })); // Let's timeout if we don't receive the ready bytes for a few seconds @@ -96,7 +84,7 @@ export default class SlvCtrlPlusSerialDeviceProvider extends SerialDeviceProvide port.unpipe(readyParser); readyParser.destroy(); reject(new Error(`Timed out while waiting for ready bytes`)); - }, 3000); + }, SlvCtrlPlusSerialDeviceProvider.SERIAL_READY_BYTE_TIMEOUT_MS); readyParser.once('ready', () => { clearTimeout(timeout); @@ -106,4 +94,24 @@ export default class SlvCtrlPlusSerialDeviceProvider extends SerialDeviceProvide }); }); } + + private async performHandshakeWithRetries(transport: DeviceBidirectionalTransport, maxAttempts: number): Promise { + let lastError; + + for (let i = 1; i <= maxAttempts; i++) { + try { + await transport.sendAndAwaitReceive( + Buffer.from(`clear`), + SlvCtrlPlusSerialDeviceProvider.HANDSHAKE_TIMEOUT_MS, + ); + return; + } catch (e: unknown) { + const error = BaseError.normalize(e); + this.logger.info(`Retrying because handshake attempt ${i} failed: ${error.message}`); + if (i === maxAttempts) lastError = e; + } + } + + throw lastError; + } } diff --git a/src/device/protocol/slvCtrlPlus/slvCtrlProtocol.ts b/src/device/protocol/slvCtrlPlus/slvCtrlProtocol.ts index e429c645..5d319617 100644 --- a/src/device/protocol/slvCtrlPlus/slvCtrlProtocol.ts +++ b/src/device/protocol/slvCtrlPlus/slvCtrlProtocol.ts @@ -1,5 +1,6 @@ -import { SlvCtrlPlusDeviceAttributes } from './slvCtrlPlusDevice.js'; -import DeviceProtocol, { DecodeResult, InferMessage, InferResponse, MessageWithResponse } from '../deviceProtocol.js'; +import type { SlvCtrlPlusDeviceAttributes } from './slvCtrlPlusDevice.js'; +import type { DecodeResult, InferMessage, InferResponse, MessageWithResponse } from '../deviceProtocol.js'; +import type DeviceProtocol from '../deviceProtocol.js'; export type DeviceInfo = { deviceType: string; @@ -28,12 +29,20 @@ export default abstract class SlvCtrlProtocol implements DeviceProtocol): Buffer; - public abstract decode(data: Buffer): DecodeResult>; + protected static readonly segmentSeparator = ';'; + protected static readonly attributeSeparator = ','; + protected static readonly keyValueSeparator = ':'; - public abstract getAttributes(responseData: KeyValuePairs): SlvCtrlPlusDeviceAttributes; + protected static readonly rangeSegmentCount = 3; + protected static readonly attributeSegmentCount = 3; + protected static readonly introductionSegmentCount = 3; public isResponseMatchingMessage(response: InferResponse, message: SlvCtrlProtocolMessage): boolean { return response.command === this.encode(message.message).toString(); } + + public abstract encode(command: InferMessage): Buffer; + public abstract decode(data: Buffer): DecodeResult>; + + public abstract getAttributes(responseData: KeyValuePairs): SlvCtrlPlusDeviceAttributes; } diff --git a/src/device/protocol/slvCtrlPlus/slvCtrlProtocolLegacy.ts b/src/device/protocol/slvCtrlPlus/slvCtrlProtocolLegacy.ts index 5567e732..f306bea9 100644 --- a/src/device/protocol/slvCtrlPlus/slvCtrlProtocolLegacy.ts +++ b/src/device/protocol/slvCtrlPlus/slvCtrlProtocolLegacy.ts @@ -1,4 +1,5 @@ -import DeviceAttribute, { DeviceAttributeModifier } from '../../attribute/deviceAttribute.js'; +import type DeviceAttribute from '../../attribute/deviceAttribute.js'; +import { DeviceAttributeModifier } from '../../attribute/deviceAttribute.js'; import BoolDeviceAttribute from '../../attribute/boolDeviceAttribute.js'; import FloatDeviceAttribute from '../../attribute/floatDeviceAttribute.js'; import StrDeviceAttribute from '../../attribute/strDeviceAttribute.js'; @@ -6,31 +7,20 @@ import IntRangeDeviceAttribute from '../../attribute/intRangeDeviceAttribute.js' import ListDeviceAttribute from '../../attribute/listDeviceAttribute.js'; import IntDeviceAttribute from '../../attribute/intDeviceAttribute.js'; import { Int } from '../../../util/numbers.js'; -import { SlvCtrlPlusDeviceAttributes } from './slvCtrlPlusDevice.js'; -import SlvCtrlProtocol, { +import type { SlvCtrlPlusDeviceAttributes } from './slvCtrlPlusDevice.js'; +import type { KeyValuePairs, Result, SlvCtrlProtocolMessage, } from './slvCtrlProtocol.js'; -import { DecodeResult, InferMessage, InferResponse } from '../deviceProtocol.js'; +import SlvCtrlProtocol from './slvCtrlProtocol.js'; +import type { DecodeResult, InferMessage, InferResponse } from '../deviceProtocol.js'; import { hasExactLength } from '../../../util/typeUtils.js'; -type SetAttributeResponse = { - command: string; - value: string; - status: string; -}; - export type StatusResponse = Record; export default class SlvCtrlProtocolLegacy extends SlvCtrlProtocol { - private static readonly commandSeparator = ';'; - - private static readonly attributeSeparator = ','; - - private static readonly attributeNameValueSeparator = ':'; - - public encode(command: InferMessage): Buffer { + public override encode(command: InferMessage): Buffer { let commandToSend = command.command; let commandArgs; @@ -47,25 +37,23 @@ export default class SlvCtrlProtocolLegacy extends SlvCtrlProtocol return Buffer.from(`${commandToSend}${argsSuffixed}`, 'utf-8'); } - public decode(rawData: Buffer): DecodeResult> { - const [command, data, result] = rawData.toString('utf-8').split(';'); + public override decode(rawData: Buffer): DecodeResult> { + const [command, data, result] = rawData.toString('utf-8').split(SlvCtrlProtocol.segmentSeparator); if (undefined === command || undefined === data) { return { error: { type: 'invalid_frame', reason: 'Mandatory segment missing' } }; } const keyValuePairs: KeyValuePairs = {}; - const unparsedKeyValuePairs = data.split(','); + const unparsedKeyValuePairs = data.split(SlvCtrlProtocol.attributeSeparator); if (command.startsWith('set-') && hasExactLength(unparsedKeyValuePairs, 1)) { - keyValuePairs.value = unparsedKeyValuePairs[0]; - } else if (command === 'introduce' && hasExactLength(unparsedKeyValuePairs, 3)) { - keyValuePairs.type = unparsedKeyValuePairs[0]; - keyValuePairs.fw = unparsedKeyValuePairs[1]; - keyValuePairs.protocol = unparsedKeyValuePairs[2]; + [keyValuePairs.value] = unparsedKeyValuePairs; + } else if (command === 'introduce' && hasExactLength(unparsedKeyValuePairs, SlvCtrlProtocol.introductionSegmentCount)) { + [keyValuePairs.type, keyValuePairs.fw, keyValuePairs.protocol] = unparsedKeyValuePairs; } else { for (const foo of unparsedKeyValuePairs) { - const [key, value] = foo.split(':'); + const [key, value] = foo.split(SlvCtrlProtocol.keyValueSeparator); if (undefined !== key && '' !== key && undefined !== value) { keyValuePairs[key] = value; @@ -77,28 +65,15 @@ export default class SlvCtrlProtocolLegacy extends SlvCtrlProtocol message: { command: command, data: keyValuePairs, - result: undefined === result ? { status: 'ok' } : this.parseResult(result), + result: undefined === result ? { status: 'ok' } : SlvCtrlProtocolLegacy.parseResult(result), }, }; } - public getAttributes(responseData: KeyValuePairs): SlvCtrlPlusDeviceAttributes { + public override getAttributes(responseData: KeyValuePairs): SlvCtrlPlusDeviceAttributes { return SlvCtrlProtocolLegacy.parseDeviceAttributes(responseData); } - private parseResult(rawResult: string): Result - { - const [status, reason] = rawResult.split(','); - - const result: Result = { status: (status === 'ok' || status === 'error') ? status : 'unknown' }; - - if (undefined !== reason) { - result.reason = reason; - } - - return result; - } - private static parseDeviceAttributes(responseData: KeyValuePairs): SlvCtrlPlusDeviceAttributes { // attributes;connected:ro[bool],adc:rw[bool],mode:rw[118-140],levelA:rw[0-99],levelB:rw[0-99] const attributeList: SlvCtrlPlusDeviceAttributes = {}; @@ -116,63 +91,16 @@ export default class SlvCtrlProtocolLegacy extends SlvCtrlProtocol return attributeList; } - private static parseStatus(data: string): StatusResponse | undefined { - const [command, attributesData] = data.split(SlvCtrlProtocolLegacy.commandSeparator); - - if ('status' !== command || undefined === attributesData) { - return undefined; - } - - const dataObj: StatusResponse = {}; - - const dataParts = attributesData - .split(SlvCtrlProtocolLegacy.attributeSeparator) - .map(s => s.trim()) - .filter(s => s.length > 0); - - for (const dataPart of dataParts) { - const [key, value] = dataPart.split(SlvCtrlProtocolLegacy.attributeNameValueSeparator); - - if (undefined !== key && undefined !== value) { - dataObj[key] = value; - } - } - - return dataObj; - } - - private static parseAttributeSetResponse(response: string): SetAttributeResponse | undefined { - const responseParts = response.split(';'); - - if (!hasExactLength(responseParts, 3)) { - return undefined; - } - - const [command, value, statusTemp] = responseParts; - const statusParts = statusTemp.split(':'); - - if (!hasExactLength(statusParts, 2)) { - return undefined; - } - - return { - command, - value, - status: statusParts[1], - }; - } - private static createAttributeFromValue(name: string, definition: string): DeviceAttribute | undefined { const re = /^(ro|rw|wo)\[(.+?)\]$/; const reRange = /^(\d+)-(\d+)$/; const reResult = re.exec(definition); - if (null === reResult || !hasExactLength(reResult, 3)) { + if (null === reResult || !hasExactLength(reResult, SlvCtrlProtocolLegacy.attributeSegmentCount)) { return undefined; } - const type = reResult[1]; - const value = reResult[2]; + const [, type, value] = reResult; let result: RegExpExecArray | null; let resultList: string[]; @@ -188,14 +116,16 @@ export default class SlvCtrlProtocolLegacy extends SlvCtrlProtocol attr = FloatDeviceAttribute.create(name, undefined, modifier, undefined); } else if ('str' === value) { attr = StrDeviceAttribute.create(name, undefined, modifier); - } else if (null !== (result = reRange.exec(value)) && hasExactLength(result, 3)) { + } else if (null !== (result = reRange.exec(value)) && hasExactLength(result, SlvCtrlProtocol.rangeSegmentCount)) { + const [, min, max] = result; + attr = IntRangeDeviceAttribute.create( name, undefined, modifier, undefined, - Int.from(parseInt(result[1], 10)), - Int.from(parseInt(result[2], 10)), + Int.from(parseInt(min, 10)), + Int.from(parseInt(max, 10)), Int.from(1), ); } else if ((resultList = value.split('|')).length > 0) { @@ -220,4 +150,17 @@ export default class SlvCtrlProtocolLegacy extends SlvCtrlProtocol throw new Error(`Unknown attribute type: ${type}`); } + + private static parseResult(rawResult: string): Result + { + const [status, reason] = rawResult.split(','); + + const result: Result = { status: (status === 'ok' || status === 'error') ? status : 'unknown' }; + + if (undefined !== reason) { + result.reason = reason; + } + + return result; + } } diff --git a/src/device/protocol/slvCtrlPlus/slvCtrlProtocolV1.ts b/src/device/protocol/slvCtrlPlus/slvCtrlProtocolV1.ts index 74cedfc3..5b37df0e 100644 --- a/src/device/protocol/slvCtrlPlus/slvCtrlProtocolV1.ts +++ b/src/device/protocol/slvCtrlPlus/slvCtrlProtocolV1.ts @@ -1,4 +1,5 @@ -import DeviceAttribute, { DeviceAttributeModifier } from '../../attribute/deviceAttribute.js'; +import type DeviceAttribute from '../../attribute/deviceAttribute.js'; +import { DeviceAttributeModifier } from '../../attribute/deviceAttribute.js'; import BoolDeviceAttribute from '../../attribute/boolDeviceAttribute.js'; import FloatDeviceAttribute from '../../attribute/floatDeviceAttribute.js'; import StrDeviceAttribute from '../../attribute/strDeviceAttribute.js'; @@ -6,34 +7,31 @@ import IntRangeDeviceAttribute from '../../attribute/intRangeDeviceAttribute.js' import ListDeviceAttribute from '../../attribute/listDeviceAttribute.js'; import IntDeviceAttribute from '../../attribute/intDeviceAttribute.js'; import { Int } from '../../../util/numbers.js'; -import SlvCtrlProtocol, { +import type { KeyValuePairs, Result, SlvCtrlProtocolMessage, } from './slvCtrlProtocol.js'; -import { DecodeResult, InferMessage, InferResponse } from '../deviceProtocol.js'; -import { SlvCtrlPlusDeviceAttributes } from './slvCtrlPlusDevice.js'; +import SlvCtrlProtocol from './slvCtrlProtocol.js'; +import type { DecodeResult, InferMessage, InferResponse } from '../deviceProtocol.js'; +import type { SlvCtrlPlusDeviceAttributes } from './slvCtrlPlusDevice.js'; import { hasExactLength } from '../../../util/typeUtils.js'; export default class SlvCtrlProtocolV1 extends SlvCtrlProtocol { - private static readonly segmentSeparator = ';'; + private static readonly responseSegmentCount = 3; - private static readonly attributeSeparator = ','; - - private static readonly keyValueSeparator = ':'; - - public encode(command: InferMessage): Buffer { + public override encode(command: InferMessage): Buffer { const argsToSend = command.args.map(arg => (typeof arg === 'boolean' ? Number(arg) : arg).toString()); const argsSuffixed = argsToSend.length > 0 ? ` ${argsToSend.join(' ')}` : ''; return Buffer.from(`${command.command}${argsSuffixed}`, 'utf-8'); } - public decode(data: Buffer): DecodeResult> { + public override decode(data: Buffer): DecodeResult> { return SlvCtrlProtocolV1.parseResponse(data.toString('utf-8')); } - public getAttributes(responseData: KeyValuePairs): SlvCtrlPlusDeviceAttributes { + public override getAttributes(responseData: KeyValuePairs): SlvCtrlPlusDeviceAttributes { const attributeList: SlvCtrlPlusDeviceAttributes = {}; for (const [attrName, attrDef] of Object.entries(responseData)) { @@ -55,16 +53,15 @@ export default class SlvCtrlProtocolV1 extends SlvCtrlProtocol const reList = /^(int|str)\(([^|()]+(\|[^|()]+)*)\)$/; const reResult = re.exec(definition); - if (null === reResult || !hasExactLength(reResult, 3)) { + if (null === reResult || !hasExactLength(reResult, SlvCtrlProtocol.attributeSegmentCount)) { return undefined; } - const type = reResult[1]; - const value = reResult[2]; + const [, modifierStr, value] = reResult; let result: RegExpExecArray | null; let resultList: string[]; - const modifier = this.getAttributeTypeFromStr(type); + const modifier = this.getAttributeTypeFromStr(modifierStr); let attr; @@ -77,10 +74,9 @@ export default class SlvCtrlProtocolV1 extends SlvCtrlProtocol } else if ('str' === value) { attr = StrDeviceAttribute.create(name, undefined, modifier); } else if (null !== (result = reRange.exec(value))) { - if ('int' === result[1]) { - const min = result[2]; - const max = result[3]; + const [, type, min, max] = result; + if ('int' === type) { if (undefined === min || undefined === max) { throw new Error(`Malformed range attribute definition: ${value}`); } @@ -140,9 +136,9 @@ export default class SlvCtrlProtocolV1 extends SlvCtrlProtocol } private static parseResponse(response: string): DecodeResult> { - const segments = response.split(SlvCtrlProtocolV1.segmentSeparator); + const segments = response.split(SlvCtrlProtocol.segmentSeparator); - if (!hasExactLength(segments, 3)) { + if (!hasExactLength(segments, SlvCtrlProtocolV1.responseSegmentCount)) { return { error: { type: 'invalid_frame', reason: `Unexpected segment count (${segments.length})` } }; } diff --git a/src/device/protocol/virtual/audio/piperVirtualDeviceConfig.ts b/src/device/protocol/virtual/audio/piperVirtualDeviceConfig.ts index 87489daf..ea630e4c 100644 --- a/src/device/protocol/virtual/audio/piperVirtualDeviceConfig.ts +++ b/src/device/protocol/virtual/audio/piperVirtualDeviceConfig.ts @@ -1,4 +1,5 @@ -import { Type, Static } from '@sinclair/typebox'; +import type { Static } from '@sinclair/typebox'; +import { Type } from '@sinclair/typebox'; export const piperVirtualDeviceConfigSchema = Type.Object({ binary: Type.Optional(Type.String()), diff --git a/src/device/protocol/virtual/audio/piperVirtualDeviceLogic.ts b/src/device/protocol/virtual/audio/piperVirtualDeviceLogic.ts index 10594f8d..4f8fbcfc 100644 --- a/src/device/protocol/virtual/audio/piperVirtualDeviceLogic.ts +++ b/src/device/protocol/virtual/audio/piperVirtualDeviceLogic.ts @@ -1,19 +1,21 @@ -import { ChildProcessByStdio } from 'node:child_process'; +import type { ChildProcessByStdio } from 'node:child_process'; import Speaker from 'speaker'; import fs from 'fs'; -import { Readable, Writable } from 'stream'; +import type { Readable, Writable } from 'stream'; import { DeviceAttributeModifier } from '../../../attribute/deviceAttribute.js'; import StrDeviceAttribute from '../../../attribute/strDeviceAttribute.js'; -import VirtualDevice from '../virtualDevice.js'; +import type VirtualDevice from '../virtualDevice.js'; import BoolDeviceAttribute from '../../../attribute/boolDeviceAttribute.js'; -import Logger from '../../../../logging/Logger.js'; +import type Logger from '../../../../logging/Logger.js'; import { spawnProcess } from '../../../../util/process.js'; import DeviceState from '../../../deviceState.js'; -import { PiperVirtualDeviceConfig } from './piperVirtualDeviceConfig.js'; +import type { PiperVirtualDeviceConfig } from './piperVirtualDeviceConfig.js'; import DevNullStream from '../../../../util/devNullStream.js'; import VirtualDeviceLogic from '../virtualDeviceLogic.js'; -import { Static, Type } from '@sinclair/typebox'; +import type { Static } from '@sinclair/typebox'; +import { Type } from '@sinclair/typebox'; import { Value } from '@sinclair/typebox/value'; +import { BITS_PER_BYTE } from '../../../../util/numbers.js'; const PiperModelMetadataSchema = Type.Object({ num_speakers: Type.Optional(Type.Number()), @@ -34,9 +36,12 @@ export default class PiperVirtualDeviceLogic extends VirtualDeviceLogic< PiperVirtualDeviceAttributes, PiperVirtualDeviceConfig > { + private static readonly stdoutDrainIdleTimeoutMs = 500; private static readonly textAttrName: string = 'text'; private static readonly queuingAttrName: string = 'queuing'; + public readonly refreshInterval = 175; + private readonly logger: Logger; private piperProcess?: ChildProcessByStdio; @@ -92,7 +97,7 @@ export default class PiperVirtualDeviceLogic extends VirtualDeviceLogic< } } - public configureAttributes(): PiperVirtualDeviceAttributes { + public override configureAttributes(): PiperVirtualDeviceAttributes { return { text: StrDeviceAttribute.create( PiperVirtualDeviceLogic.textAttrName, @@ -109,7 +114,17 @@ export default class PiperVirtualDeviceLogic extends VirtualDeviceLogic< }; } - public readonly refreshInterval = 175; + public override async destroy(): Promise { + this.stopPlayback(); + + if (this.piperProcess !== undefined) { + this.piperProcess.stdin.destroy(); + this.piperProcess.kill(); + this.piperProcess = undefined; + } + + return Promise.resolve(); + } private async startPiper(): Promise { if (undefined !== this.piperProcess) { @@ -167,7 +182,7 @@ export default class PiperVirtualDeviceLogic extends VirtualDeviceLogic< if (undefined === metadata?.sample_width) { this.logger.warn(`Bit depth missing from piper model metadata file, falling back to ${bitDepth}`); } else { - bitDepth = metadata.sample_width * 8; + bitDepth = metadata.sample_width * BITS_PER_BYTE; } let sampleRate = 22050; @@ -223,25 +238,13 @@ export default class PiperVirtualDeviceLogic extends VirtualDeviceLogic< this.piperProcess.stdout.pipe(this.speaker); } - public override destroy(): Promise { - this.stopPlayback(); - - if (this.piperProcess !== undefined) { - this.piperProcess.stdin.destroy(); - this.piperProcess.kill(); - this.piperProcess = undefined; - } - - return Promise.resolve(); - } - private stopPlayback(): boolean { if (undefined === this.piperProcess || undefined === this.speaker) { return false; } this.piperProcess.stdout.unpipe(); - const devNull = new DevNullStream(500); + const devNull = new DevNullStream(PiperVirtualDeviceLogic.stdoutDrainIdleTimeoutMs); devNull.on('idle', () => { this.speakerCoolDown = false; this.piperProcess?.stdout.unpipe(); diff --git a/src/device/protocol/virtual/audio/ttsVirtualDeviceConfig.ts b/src/device/protocol/virtual/audio/ttsVirtualDeviceConfig.ts index 08683cb6..8fc319a4 100644 --- a/src/device/protocol/virtual/audio/ttsVirtualDeviceConfig.ts +++ b/src/device/protocol/virtual/audio/ttsVirtualDeviceConfig.ts @@ -1,4 +1,5 @@ -import { Type, Static } from '@sinclair/typebox'; +import type { Static } from '@sinclair/typebox'; +import { Type } from '@sinclair/typebox'; type TtsVirtualDeviceConfigSchema = typeof ttsVirtualDeviceConfigSchema; diff --git a/src/device/protocol/virtual/audio/ttsVirtualDeviceLogic.ts b/src/device/protocol/virtual/audio/ttsVirtualDeviceLogic.ts index 341765bf..d565b484 100644 --- a/src/device/protocol/virtual/audio/ttsVirtualDeviceLogic.ts +++ b/src/device/protocol/virtual/audio/ttsVirtualDeviceLogic.ts @@ -2,12 +2,12 @@ import { DeviceAttributeModifier } from '../../../attribute/deviceAttribute.js'; import StrDeviceAttribute from '../../../attribute/strDeviceAttribute.js'; import VirtualDeviceLogic from '../virtualDeviceLogic.js'; import say from 'say'; -import VirtualDevice from '../virtualDevice.js'; +import type VirtualDevice from '../virtualDevice.js'; import BoolDeviceAttribute from '../../../attribute/boolDeviceAttribute.js'; import IntDeviceAttribute from '../../../attribute/intDeviceAttribute.js'; import { Int } from '../../../../util/numbers.js'; -import Logger from '../../../../logging/Logger.js'; -import { TtsVirtualDeviceConfig } from './ttsVirtualDeviceConfig.js'; +import type Logger from '../../../../logging/Logger.js'; +import type { TtsVirtualDeviceConfig } from './ttsVirtualDeviceConfig.js'; type TtsVirtualDeviceAttributes = { text: StrDeviceAttribute; @@ -25,6 +25,8 @@ export default class TtsVirtualDeviceLogic extends VirtualDeviceLogic< private static readonly queuingAttrName: string = 'queuing'; private static readonly queueLengthAttrName: string = 'queueLength'; + public readonly refreshInterval = 175; + private ttsEntries: string[] = []; private readonly logger: Logger; @@ -79,9 +81,7 @@ export default class TtsVirtualDeviceLogic extends VirtualDeviceLogic< await device.setAttribute('queueLength', Int.from(this.ttsEntries.length)); } - public readonly refreshInterval = 175; - - public configureAttributes(): TtsVirtualDeviceAttributes { + public override configureAttributes(): TtsVirtualDeviceAttributes { const textAttr = StrDeviceAttribute.create( TtsVirtualDeviceLogic.textAttrName, 'Text', DeviceAttributeModifier.writeOnly, ); diff --git a/src/device/protocol/virtual/display/displayVirtualDeviceLogic.ts b/src/device/protocol/virtual/display/displayVirtualDeviceLogic.ts index a9d194db..ca970b56 100644 --- a/src/device/protocol/virtual/display/displayVirtualDeviceLogic.ts +++ b/src/device/protocol/virtual/display/displayVirtualDeviceLogic.ts @@ -1,25 +1,29 @@ import { DeviceAttributeModifier } from '../../../attribute/deviceAttribute.js'; import StrDeviceAttribute from '../../../attribute/strDeviceAttribute.js'; import VirtualDeviceLogic from '../virtualDeviceLogic.js'; -import { NoDeviceConfig } from '../../../deviceConfig.js'; +import type { NoDeviceConfig } from '../../../deviceConfig.js'; type DisplayVirtualDeviceAttributes = { content: StrDeviceAttribute; }; export default class DisplayVirtualDeviceLogic extends VirtualDeviceLogic { + private static readonly REFRESH_INTERVAL_MS = 175; + public constructor(config: NoDeviceConfig) { super(config); } - public async refreshData(): Promise { + public override async refreshData(): Promise { // no-op, because it doesn't read anything from anywhere return Promise.resolve(); } - public readonly refreshInterval = 175; + public override get refreshInterval(): number { + return DisplayVirtualDeviceLogic.REFRESH_INTERVAL_MS; + } - public configureAttributes(): DisplayVirtualDeviceAttributes { + public override configureAttributes(): DisplayVirtualDeviceAttributes { const contentAttr = StrDeviceAttribute.create( 'content', 'Content', DeviceAttributeModifier.readWrite, ); diff --git a/src/device/protocol/virtual/genericVirtualDeviceFactory.ts b/src/device/protocol/virtual/genericVirtualDeviceFactory.ts index c94a34b2..bdd47e1d 100644 --- a/src/device/protocol/virtual/genericVirtualDeviceFactory.ts +++ b/src/device/protocol/virtual/genericVirtualDeviceFactory.ts @@ -1,13 +1,15 @@ -import { Static, TObject } from '@sinclair/typebox'; -import { AnyVirtualDeviceLogic, ExtractConfig } from './virtualDeviceLogic.js'; -import DateFactory from '../../../factory/dateFactory.js'; -import JsonSchemaValidatorFactory from '../../../schemaValidation/JsonSchemaValidatorFactory.js'; -import KnownDevice from '../../../settings/knownDevice.js'; -import VirtualDevice, { AnyVirtualDevice } from './virtualDevice.js'; -import VirtualDeviceFactory from './virtualDeviceFactory.js'; -import VirtualDeviceLogicFactory from './virtualDeviceLogicFactory.js'; -import Logger from '../../../logging/Logger.js'; -import EventEmitterFactory from '../../../factory/eventEmitterFactory.js'; +import type { Static, TObject } from '@sinclair/typebox'; +import type { AnyVirtualDeviceLogic, ExtractConfig } from './virtualDeviceLogic.js'; +import type DateFactory from '../../../factory/dateFactory.js'; +import type JsonSchemaValidatorFactory from '../../../schemaValidation/JsonSchemaValidatorFactory.js'; +import type KnownDevice from '../../../settings/knownDevice.js'; +import type { AnyVirtualDevice } from './virtualDevice.js'; +import VirtualDevice from './virtualDevice.js'; +import type VirtualDeviceFactory from './virtualDeviceFactory.js'; +import type VirtualDeviceLogicFactory from './virtualDeviceLogicFactory.js'; +import type Logger from '../../../logging/Logger.js'; +import type EventEmitterFactory from '../../../factory/eventEmitterFactory.js'; +import { JSON_INDENTION_SPACES } from '../../../util/numbers.js'; type LogicFactoryAndConfigTuple = { deviceLogicFactory: VirtualDeviceLogicFactory; @@ -58,7 +60,7 @@ export default class GenericVirtualDeviceFactory implements VirtualDeviceFactory return this; } - public create(knownDevice: KnownDevice, provider: string): Promise { + public async create(knownDevice: KnownDevice, provider: string): Promise { return new Promise(resolve => { const factoryName = `${GenericVirtualDeviceFactory.capitalizeFirstLetter(knownDevice.type)}VirtualDeviceLogic`; const factory = this.logicFactories.get(factoryName); @@ -72,18 +74,21 @@ export default class GenericVirtualDeviceFactory implements VirtualDeviceFactory if (!isConfigValid) { const validationErrors = jsonSchemaValidator.getValidationErrors(); - throw new Error(`Config for device is not valid: ${JSON.stringify(validationErrors, null, 2)}`); + throw new Error(`Config for device is not valid: ${JSON.stringify(validationErrors, null, JSON_INDENTION_SPACES)}`); } const deviceLogic = factory.deviceLogicFactory.create(knownDevice.config); const device = new VirtualDevice( + { + deviceId: knownDevice.id, + deviceName: knownDevice.name, + provider: provider, + connectedSince: this.dateFactory.now(), + controllable: false, + }, '1.0.0', - knownDevice.id, - knownDevice.name, knownDevice.type, - provider, - this.dateFactory.now(), knownDevice.config, deviceLogic, this.eventEmitterFactory.create(), diff --git a/src/device/protocol/virtual/genericVirtualDeviceLogicFactory.ts b/src/device/protocol/virtual/genericVirtualDeviceLogicFactory.ts index 5ffc9a6d..2a6f6e9b 100644 --- a/src/device/protocol/virtual/genericVirtualDeviceLogicFactory.ts +++ b/src/device/protocol/virtual/genericVirtualDeviceLogicFactory.ts @@ -1,6 +1,7 @@ -import VirtualDeviceLogic, { ExtractConfig } from './virtualDeviceLogic.js'; -import Logger from '../../../logging/Logger.js'; -import VirtualDeviceLogicFactory from './virtualDeviceLogicFactory.js'; +import type { ExtractConfig } from './virtualDeviceLogic.js'; +import type VirtualDeviceLogic from './virtualDeviceLogic.js'; +import type Logger from '../../../logging/Logger.js'; +import type VirtualDeviceLogicFactory from './virtualDeviceLogicFactory.js'; type Constructor = new (config: ExtractConfig, logger: Logger) => TDeviceLogic; diff --git a/src/device/protocol/virtual/randomGenerator/randomGeneratorVirtualDeviceConfig.ts b/src/device/protocol/virtual/randomGenerator/randomGeneratorVirtualDeviceConfig.ts index f7298bb3..cd0c5f1a 100644 --- a/src/device/protocol/virtual/randomGenerator/randomGeneratorVirtualDeviceConfig.ts +++ b/src/device/protocol/virtual/randomGenerator/randomGeneratorVirtualDeviceConfig.ts @@ -1,4 +1,5 @@ -import { Type, Static } from '@sinclair/typebox'; +import type { Static } from '@sinclair/typebox'; +import { Type } from '@sinclair/typebox'; export const randomGeneratorVirtualDeviceConfigSchema = Type.Object({ min: Type.Number(), diff --git a/src/device/protocol/virtual/randomGenerator/randomGeneratorVirtualDeviceLogic.ts b/src/device/protocol/virtual/randomGenerator/randomGeneratorVirtualDeviceLogic.ts index 5a5c37a7..fc260003 100644 --- a/src/device/protocol/virtual/randomGenerator/randomGeneratorVirtualDeviceLogic.ts +++ b/src/device/protocol/virtual/randomGenerator/randomGeneratorVirtualDeviceLogic.ts @@ -1,9 +1,9 @@ import { DeviceAttributeModifier } from '../../../attribute/deviceAttribute.js'; import IntDeviceAttribute from '../../../attribute/intDeviceAttribute.js'; import VirtualDeviceLogic from '../virtualDeviceLogic.js'; -import VirtualDevice from '../virtualDevice.js'; +import type VirtualDevice from '../virtualDevice.js'; import { Int } from '../../../../util/numbers.js'; -import { RandomGeneratorVirtualDeviceConfig } from './randomGeneratorVirtualDeviceConfig.js'; +import type { RandomGeneratorVirtualDeviceConfig } from './randomGeneratorVirtualDeviceConfig.js'; type RandomGeneratorVirtualDeviceAttributes = { value: IntDeviceAttribute; @@ -13,6 +13,8 @@ export default class RandomGeneratorVirtualDeviceLogic extends VirtualDeviceLogi RandomGeneratorVirtualDeviceAttributes, RandomGeneratorVirtualDeviceConfig > { + private static readonly REFRESH_INTERVAL_MS = 100; + public constructor(config: RandomGeneratorVirtualDeviceConfig) { super(config); @@ -23,7 +25,9 @@ export default class RandomGeneratorVirtualDeviceLogic extends VirtualDeviceLogi } } - public readonly refreshInterval = 100; + public override get refreshInterval(): number { + return RandomGeneratorVirtualDeviceLogic.REFRESH_INTERVAL_MS; + } public async refreshData(device: VirtualDevice): Promise { const currentNumber = (await device.getAttribute('value'))?.value; @@ -36,7 +40,7 @@ export default class RandomGeneratorVirtualDeviceLogic extends VirtualDeviceLogi await device.setAttribute('value', Int.from(newNumber)); } - public configureAttributes(): RandomGeneratorVirtualDeviceAttributes { + public override configureAttributes(): RandomGeneratorVirtualDeviceAttributes { const valueAttr = IntDeviceAttribute.create( 'value', 'Random number', DeviceAttributeModifier.readOnly, undefined, ); diff --git a/src/device/protocol/virtual/virtualDevice.ts b/src/device/protocol/virtual/virtualDevice.ts index 09dda7fd..a55ed41c 100644 --- a/src/device/protocol/virtual/virtualDevice.ts +++ b/src/device/protocol/virtual/virtualDevice.ts @@ -1,10 +1,11 @@ import { Exclude, Expose } from 'class-transformer'; -import Device, { AttributeKeyOf, AttributeValueOf, NoDeviceNotifications } from '../../device.js'; +import type { AttributeKeyOf, AttributeValueOf, DeviceInfo, NoDeviceNotifications } from '../../device.js'; +import Device from '../../device.js'; import DeviceState from '../../deviceState.js'; -import VirtualDeviceLogic, { AnyVirtualDeviceLogic, ExtractAttributes, ExtractConfig } from './virtualDeviceLogic.js'; -import EventEmitter from 'events'; -import Logger from '../../../logging/Logger.js'; -import { DeviceId } from '../../deviceId.js'; +import type { AnyVirtualDeviceLogic, ExtractAttributes, ExtractConfig } from './virtualDeviceLogic.js'; +import type VirtualDeviceLogic from './virtualDeviceLogic.js'; +import type EventEmitter from 'events'; +import type Logger from '../../../logging/Logger.js'; import { normalizeError } from '../../../util/typeUtils.js'; export type AnyVirtualDevice = VirtualDevice; @@ -17,53 +18,31 @@ export default class VirtualDevice< TLogic extends AnyVirtualDeviceLogic, > extends Device, NoDeviceNotifications, ExtractConfig> { @Expose() - private deviceModel: string; + // eslint-disable-next-line @typescript-eslint/no-unused-private-class-members + private readonly deviceModel: string; @Expose() + // eslint-disable-next-line @typescript-eslint/no-unused-private-class-members private readonly fwVersion: string; private readonly deviceLogic: TypedDeviceLogic; - private readonly statusUpdater?: NodeJS.Timeout; - public constructor( + deviceInfo: DeviceInfo, fwVersion: string, - deviceId: DeviceId, - deviceName: string, deviceModel: string, - provider: string, - connectedSince: Date, config: ExtractConfig, deviceLogic: TypedDeviceLogic, eventEmitter: EventEmitter, logger: Logger, ) { - super(deviceId, deviceName, provider, connectedSince, false, deviceLogic.configureAttributes(), config, eventEmitter, logger); + super(deviceInfo, deviceLogic.configureAttributes(), config, eventEmitter, logger); this.deviceModel = deviceModel; this.fwVersion = fwVersion; this.deviceLogic = deviceLogic; } - protected override async doClose(): Promise { - await this.deviceLogic.destroy(); - } - - protected override async doRefresh(): Promise { - try { - await this.deviceLogic.refreshData(this); - } catch (e: unknown) { - const error = normalizeError(e); - this.state = DeviceState.error; - this.errorInfo = { - reason: error.message, - occurredAt: new Date(), - }; - - throw error; - } - } - public override get getRefreshInterval(): number { return this.deviceLogic.refreshInterval; } @@ -91,4 +70,23 @@ export default class VirtualDevice< resolve(value); }); } + + protected override async doClose(): Promise { + await this.deviceLogic.destroy(); + } + + protected override async doRefresh(): Promise { + try { + await this.deviceLogic.refreshData(this); + } catch (e: unknown) { + const error = normalizeError(e); + this.state = DeviceState.error; + this.errorInfo = { + reason: error.message, + occurredAt: new Date(), + }; + + throw error; + } + } } diff --git a/src/device/protocol/virtual/virtualDeviceFactory.ts b/src/device/protocol/virtual/virtualDeviceFactory.ts index 8d1a0584..81cbe2c4 100644 --- a/src/device/protocol/virtual/virtualDeviceFactory.ts +++ b/src/device/protocol/virtual/virtualDeviceFactory.ts @@ -1,5 +1,5 @@ -import KnownDevice from '../../../settings/knownDevice.js'; -import { AnyVirtualDevice } from './virtualDevice.js'; +import type KnownDevice from '../../../settings/knownDevice.js'; +import type { AnyVirtualDevice } from './virtualDevice.js'; type VirtualDeviceFactory = { create(knownDevice: KnownDevice, provider: string): Promise; diff --git a/src/device/protocol/virtual/virtualDeviceLogic.ts b/src/device/protocol/virtual/virtualDeviceLogic.ts index d6aac694..950df45c 100644 --- a/src/device/protocol/virtual/virtualDeviceLogic.ts +++ b/src/device/protocol/virtual/virtualDeviceLogic.ts @@ -1,6 +1,6 @@ -import VirtualDevice from './virtualDevice.js'; -import { DeviceAttributes } from '../../device.js'; -import { AnyDeviceConfig, NoDeviceConfig } from '../../deviceConfig.js'; +import type VirtualDevice from './virtualDevice.js'; +import type { DeviceAttributes } from '../../device.js'; +import type { AnyDeviceConfig, NoDeviceConfig } from '../../deviceConfig.js'; export type ExtractAttributes> = TLogic extends VirtualDeviceLogic @@ -24,13 +24,14 @@ export default abstract class VirtualDeviceLogic< this.config = config; } + // eslint-disable-next-line @typescript-eslint/class-methods-use-this + public async destroy(): Promise { + // no-op + } + public abstract refreshData(device: VirtualDevice): Promise; public abstract configureAttributes(): TAttributes; public abstract get refreshInterval(): number; - - public async destroy(): Promise { - // no-op - } } diff --git a/src/device/protocol/virtual/virtualDeviceLogicFactory.ts b/src/device/protocol/virtual/virtualDeviceLogicFactory.ts index b06a91e1..2979364a 100644 --- a/src/device/protocol/virtual/virtualDeviceLogicFactory.ts +++ b/src/device/protocol/virtual/virtualDeviceLogicFactory.ts @@ -1,4 +1,5 @@ -import VirtualDeviceLogic, { ExtractConfig } from './virtualDeviceLogic.js'; +import type { ExtractConfig } from './virtualDeviceLogic.js'; +import type VirtualDeviceLogic from './virtualDeviceLogic.js'; type VirtualDeviceLogicFactory = { create(config: ExtractConfig): TDeviceLogic; diff --git a/src/device/protocol/virtual/virtualDeviceProvider.ts b/src/device/protocol/virtual/virtualDeviceProvider.ts index 76230a27..62a00361 100644 --- a/src/device/protocol/virtual/virtualDeviceProvider.ts +++ b/src/device/protocol/virtual/virtualDeviceProvider.ts @@ -1,13 +1,13 @@ import DeviceProvider from '../../provider/deviceProvider.js'; -import Logger from '../../../logging/Logger.js'; -import { AnyVirtualDevice } from './virtualDevice.js'; -import KnownDevice from '../../../settings/knownDevice.js'; -import SettingsManager from '../../../settings/settingsManager.js'; +import type Logger from '../../../logging/Logger.js'; +import type { AnyVirtualDevice } from './virtualDevice.js'; +import type KnownDevice from '../../../settings/knownDevice.js'; +import type SettingsManager from '../../../settings/settingsManager.js'; import SettingsEventType from '../../../settings/settingsEventType.js'; import type Settings from '../../../settings/settings.js'; -import { DeviceDetectionInfo } from '../../deviceManager.js'; -import VirtualDeviceFactory from './virtualDeviceFactory.js'; -import DeviceManager from '../../deviceManager.js'; +import type { DeviceDetectionInfo } from '../../deviceManager.js'; +import type VirtualDeviceFactory from './virtualDeviceFactory.js'; +import type DeviceManager from '../../deviceManager.js'; import { asyncHandler } from '../../../util/async.js'; import { logError } from '../../../util/error.js'; import { DetectionId } from '../../deviceId.js'; @@ -49,7 +49,7 @@ export default class VirtualDeviceProvider extends DeviceProvider { + protected override async doStop(): Promise { this.settingsManager.off(SettingsEventType.changed, this.settingsChangedListener); return Promise.resolve(); @@ -59,7 +59,7 @@ export default class VirtualDeviceProvider extends DeviceProvider { + protected override async createDevice(deviceDetectionInfo: VirtualDeviceDetectionInfo): Promise { this.logger.info(`Virtual device detected: ${deviceDetectionInfo.knownDevice.name}`, deviceDetectionInfo.knownDevice); return this.deviceFactory.create(deviceDetectionInfo.knownDevice, VirtualDeviceProvider.providerName); diff --git a/src/device/protocol/virtual/virtualDeviceProviderFactory.ts b/src/device/protocol/virtual/virtualDeviceProviderFactory.ts index ad12b9f9..53ef7111 100644 --- a/src/device/protocol/virtual/virtualDeviceProviderFactory.ts +++ b/src/device/protocol/virtual/virtualDeviceProviderFactory.ts @@ -1,9 +1,9 @@ -import DeviceProviderFactory from '../../provider/deviceProviderFactory.js'; -import Logger from '../../../logging/Logger.js'; +import type DeviceProviderFactory from '../../provider/deviceProviderFactory.js'; +import type Logger from '../../../logging/Logger.js'; import VirtualDeviceProvider from './virtualDeviceProvider.js'; -import SettingsManager from '../../../settings/settingsManager.js'; -import VirtualDeviceFactory from './virtualDeviceFactory.js'; -import DeviceManager from '../../deviceManager.js'; +import type SettingsManager from '../../../settings/settingsManager.js'; +import type VirtualDeviceFactory from './virtualDeviceFactory.js'; +import type DeviceManager from '../../deviceManager.js'; export default class VirtualDeviceProviderFactory implements DeviceProviderFactory { diff --git a/src/device/protocol/zc95/zc95Device.ts b/src/device/protocol/zc95/zc95Device.ts index 61892de5..67e5ad6b 100644 --- a/src/device/protocol/zc95/zc95Device.ts +++ b/src/device/protocol/zc95/zc95Device.ts @@ -1,37 +1,47 @@ import { Exclude, Expose } from 'class-transformer'; -import { AttributeKeyOf, AttributeValueOf, DeviceAttributeOf } from '../../device.js'; -import Zc95MessageFactory, { +import type { AttributeKeyOf, AttributeValueOf, DeviceAttributeOf, DeviceInfo } from '../../device.js'; +import type { MenuItem, MinMaxMenuItem, MultiChoiceMenuItem, PowerStatusMsgResponse, } from './zc95MessageFactory.js'; -import IntRangeDeviceAttribute, { +import type Zc95MessageFactory from './zc95MessageFactory.js'; +import type { InitializedIntRangeDeviceAttribute, } from '../../attribute/intRangeDeviceAttribute.js'; -import ListDeviceAttribute, { InitializedListDeviceAttribute } from '../../attribute/listDeviceAttribute.js'; -import { InitializedBoolDeviceAttribute } from '../../attribute/boolDeviceAttribute.js'; +import IntRangeDeviceAttribute from '../../attribute/intRangeDeviceAttribute.js'; +import type { InitializedListDeviceAttribute } from '../../attribute/listDeviceAttribute.js'; +import ListDeviceAttribute from '../../attribute/listDeviceAttribute.js'; +import type { InitializedBoolDeviceAttribute } from '../../attribute/boolDeviceAttribute.js'; import { DeviceAttributeModifier } from '../../attribute/deviceAttribute.js'; import { Int } from '../../../util/numbers.js'; import { getTypedKeys } from '../../../util/objects.js'; -import { AllOrNone } from '../../../types.js'; -import { NoDeviceConfig } from '../../deviceConfig.js'; +import type { AllOrNone } from '../../../types.js'; +import type { NoDeviceConfig } from '../../deviceConfig.js'; import PeripheralDevice from '../../peripheralDevice.js'; -import Zc95Protocol, { MsgResponse } from './zc95Protocol.js'; +import type { MsgResponse } from './zc95Protocol.js'; +import type Zc95Protocol from './zc95Protocol.js'; import typeDetect from 'type-detect'; -import BidirectionalDeviceTransport from '../../transport/deviceBidirectionalTransport.js'; -import MessageResponseHandler from '../messageResponseHandler.js'; -import Logger from '../../../logging/Logger.js'; -import EventEmitter from 'events'; -import { DeviceId } from '../../deviceId.js'; +import type BidirectionalDeviceTransport from '../../transport/deviceBidirectionalTransport.js'; +import type MessageResponseHandler from '../messageResponseHandler.js'; +import type Logger from '../../../logging/Logger.js'; +import type EventEmitter from 'events'; type RequiredZc95DeviceAttributes = { activePattern: InitializedListDeviceAttribute; patternStarted: InitializedBoolDeviceAttribute; }; +enum Zc95DevicePowerChannelIndex { + One = 1, + Two = 2, + Three = 3, + Four = 4, +} + type Zc95DevicePowerChannelAttributesKeyPrefix = `powerChannel`; -type Zc95DevicePowerChannelAttributesKey = `${Zc95DevicePowerChannelAttributesKeyPrefix}${1 | 2 | 3 | 4}`; +type Zc95DevicePowerChannelAttributesKey = `${Zc95DevicePowerChannelAttributesKeyPrefix}${Zc95DevicePowerChannelIndex}`; type Zc95DevicePowerChannelAttributes = Record; @@ -50,26 +60,24 @@ type AttributeValue = AttributeValueOf { + private static readonly powerScaleFactor = 10; private static readonly patternAttributePrefix = 'patternAttribute'; private static readonly powerChannelAttributePrefix = 'powerChannel'; @Expose() + // eslint-disable-next-line @typescript-eslint/no-unused-private-class-members private readonly fwVersion: string; - private msgFactory: Zc95MessageFactory; + private readonly msgFactory: Zc95MessageFactory; private readonly messageResponseHandler: MessageResponseHandler; public constructor( - deviceId: DeviceId, - deviceName: string, - provider: string, - connectedSince: Date, + deviceInfo: DeviceInfo, fwVersion: string, protocol: Zc95Protocol, transport: BidirectionalDeviceTransport, - controllable: boolean, attributes: Zc95DeviceAttributes, config: NoDeviceConfig, msgFactory: Zc95MessageFactory, @@ -77,7 +85,7 @@ export default class Zc95Device extends PeripheralDevice, value: number, ): Promise { - if (!this.allPowerChannelValuesDefined(this.attributes)) { + if (!Zc95Device.allPowerChannelValuesDefined(this.attributes)) { throw new Error('Cannot set channel power before all channel values have been initialized'); } @@ -163,27 +171,17 @@ export default class Zc95Device extends PeripheralDevice): attrs is { - [K in keyof Zc95DevicePowerChannelAttributes]-?: InitializedIntRangeDeviceAttribute - } { - return attrs.powerChannel1?.value !== undefined - && attrs.powerChannel2?.value !== undefined - && attrs.powerChannel3?.value !== undefined - && attrs.powerChannel4?.value !== undefined - ; - } - private async setAttributeActivePattern(value: number): Promise { if (this.attributes.activePattern.value === value) { return; @@ -206,48 +204,26 @@ export default class Zc95Device extends PeripheralDevice { - if ( - key.startsWith(Zc95Device.patternAttributePrefix) + if (key.startsWith(Zc95Device.patternAttributePrefix) || key.startsWith(Zc95Device.powerChannelAttributePrefix) ) { Reflect.deleteProperty(this.attributes, key); @@ -259,7 +235,7 @@ export default class Zc95Device extends PeripheralDevice( attrName, menuItem.Title, @@ -325,39 +301,70 @@ export default class Zc95Device extends PeripheralDevice): attrs is { + [K in keyof Zc95DevicePowerChannelAttributes]-?: InitializedIntRangeDeviceAttribute + } { + return attrs.powerChannel1?.value !== undefined + && attrs.powerChannel2?.value !== undefined + && attrs.powerChannel3?.value !== undefined + && attrs.powerChannel4?.value !== undefined + ; + } + + private static isPowerChannelAttribute( attribute: AnyZc95DeviceAttribute, ): attribute is DeviceAttributeOf { return attribute.name.startsWith(Zc95Device.powerChannelAttributePrefix) && ['1', '2', '3', '4'].includes(attribute.name.slice(Zc95Device.powerChannelAttributePrefix.length)); } - private isPatternStartedAttribute(attribute: AnyZc95DeviceAttribute): attribute is Zc95DeviceAttributes['patternStarted'] & { name: 'patternStarted' } { + private static isPatternStartedAttribute(attribute: AnyZc95DeviceAttribute): attribute is Zc95DeviceAttributes['patternStarted'] & { name: 'patternStarted' } { return attribute.name === 'patternStarted'; } - private isActivePatternAttribute(attribute: AnyZc95DeviceAttribute): attribute is Zc95DeviceAttributes['activePattern'] & { name: 'activePattern' } { + private static isActivePatternAttribute(attribute: AnyZc95DeviceAttribute): attribute is Zc95DeviceAttributes['activePattern'] & { name: 'activePattern' } { return attribute.name === 'activePattern'; } - private isPatternDetailAttribute(attribute: AnyZc95DeviceAttribute): attribute is DeviceAttributeOf { + private static isPatternDetailAttribute(attribute: AnyZc95DeviceAttribute): attribute is DeviceAttributeOf { return attribute.name.startsWith(Zc95Device.patternAttributePrefix) && !isNaN(parseInt(attribute.name.slice(Zc95Device.patternAttributePrefix.length), 10)); } - private isPowerStatusMessage(msg: MsgResponse): msg is PowerStatusMsgResponse { + private static isPowerStatusMessage(msg: MsgResponse): msg is PowerStatusMsgResponse { return msg.MsgId === -1 && msg.Type === 'PowerStatus'; } - private isMinMaxMenuItem(menuItem: MenuItem): menuItem is MinMaxMenuItem { + private static isMinMaxMenuItem(menuItem: MenuItem): menuItem is MinMaxMenuItem { return menuItem.Type === 'MIN_MAX'; } - private isMultiChoiceMenuItem(menuItem: MenuItem): menuItem is MultiChoiceMenuItem { + private static isMultiChoiceMenuItem(menuItem: MenuItem): menuItem is MultiChoiceMenuItem { return menuItem.Type === 'MULTI_CHOICE'; } - private assertOkResponse(response: MsgResponse): void + private static assertOkResponse(response: MsgResponse): void { if (response.Result !== 'OK') { const error = response.Error ?? ''; diff --git a/src/device/protocol/zc95/zc95DeviceFactory.ts b/src/device/protocol/zc95/zc95DeviceFactory.ts index fc9ec008..95105ea3 100644 --- a/src/device/protocol/zc95/zc95DeviceFactory.ts +++ b/src/device/protocol/zc95/zc95DeviceFactory.ts @@ -1,21 +1,27 @@ -import KnownDeviceRegistry from '../../knownDeviceRegistry.js'; -import DateFactory from '../../../factory/dateFactory.js'; -import Logger from '../../../logging/Logger.js'; -import Zc95Device, { Zc95DeviceAttributes } from './zc95Device.js'; -import Zc95MessageFactory, { VersionMsgResponse } from './zc95MessageFactory.js'; +import type KnownDeviceRegistry from '../../knownDeviceRegistry.js'; +import type DateFactory from '../../../factory/dateFactory.js'; +import type Logger from '../../../logging/Logger.js'; +import type { Zc95DeviceAttributes } from './zc95Device.js'; +import Zc95Device from './zc95Device.js'; +import type { VersionMsgResponse } from './zc95MessageFactory.js'; +import type Zc95MessageFactory from './zc95MessageFactory.js'; import { DeviceAttributeModifier } from '../../attribute/deviceAttribute.js'; -import ListDeviceAttribute, { ListDeviceAttributeOptions } from '../../attribute/listDeviceAttribute.js'; +import type { ListDeviceAttributeOptions } from '../../attribute/listDeviceAttribute.js'; +import ListDeviceAttribute from '../../attribute/listDeviceAttribute.js'; import BoolDeviceAttribute from '../../attribute/boolDeviceAttribute.js'; import { Int } from '../../../util/numbers.js'; -import Zc95Protocol from './zc95Protocol.js'; -import DeviceBidirectionalTransport from '../../transport/deviceBidirectionalTransport.js'; -import MessageResponseHandler from '../messageResponseHandler.js'; -import EventEmitterFactory from '../../../factory/eventEmitterFactory.js'; +import type Zc95Protocol from './zc95Protocol.js'; +import type DeviceBidirectionalTransport from '../../transport/deviceBidirectionalTransport.js'; +import type MessageResponseHandler from '../messageResponseHandler.js'; +import type EventEmitterFactory from '../../../factory/eventEmitterFactory.js'; import { logError } from '../../../util/error.js'; -import { DeviceId, DetectionId } from '../../deviceId.js'; +import type { DetectionId } from '../../deviceId.js'; +import { DeviceId } from '../../deviceId.js'; export default class Zc95DeviceFactory { + private static readonly PATTERN_LIST_RESPONSE_TIMEOUT_MS = 2000; + private readonly dateFactory: DateFactory; private readonly eventEmitterFactory: EventEmitterFactory; @@ -48,10 +54,10 @@ export default class Zc95DeviceFactory try { const availablePatterns = (await messageResponseHandler.send( messageFactory.createGetPatterns(), - 2000, + Zc95DeviceFactory.PATTERN_LIST_RESPONSE_TIMEOUT_MS, )).Patterns; - const attributes = this.getAttributes( + const attributes = Zc95DeviceFactory.getAttributes( availablePatterns.map(pattern => ({ key: Int.from(pattern.Id), value: pattern.Name })), ); @@ -65,14 +71,16 @@ export default class Zc95DeviceFactory ); const device = new Zc95Device( - knownDevice.id, - knownDevice.name, - provider, - this.dateFactory.now(), + { + deviceId: knownDevice.id, + deviceName: knownDevice.name, + provider, + connectedSince: this.dateFactory.now(), + controllable: true, + }, versionDetails.ZC95, protocol, transport, - true, attributes, {}, messageFactory, @@ -93,7 +101,7 @@ export default class Zc95DeviceFactory } } - private getAttributes(patterns: ListDeviceAttributeOptions): Zc95DeviceAttributes { + private static getAttributes(patterns: ListDeviceAttributeOptions): Zc95DeviceAttributes { const activePatternAttr = ListDeviceAttribute.createInitialized( 'activePattern', 'Pattern', DeviceAttributeModifier.readWrite, patterns, Int.ZERO, ); diff --git a/src/device/protocol/zc95/zc95MessageFactory.ts b/src/device/protocol/zc95/zc95MessageFactory.ts index 63dad057..f93c7d4e 100644 --- a/src/device/protocol/zc95/zc95MessageFactory.ts +++ b/src/device/protocol/zc95/zc95MessageFactory.ts @@ -1,4 +1,5 @@ -import Zc95Protocol, { Msg, MsgAndResponseIdentifier, MsgResponse } from './zc95Protocol.js'; +import type { Msg, MsgAndResponseIdentifier, MsgResponse } from './zc95Protocol.js'; +import Zc95Protocol from './zc95Protocol.js'; export type GetPatternDetailMsg = { Type: 'GetPatternDetail'; diff --git a/src/device/protocol/zc95/zc95Protocol.ts b/src/device/protocol/zc95/zc95Protocol.ts index cccb9f0f..817d6d4c 100644 --- a/src/device/protocol/zc95/zc95Protocol.ts +++ b/src/device/protocol/zc95/zc95Protocol.ts @@ -1,7 +1,9 @@ -import DeviceProtocol, { DecodeResult, InferMessage, InferResponse, MessageWithResponse } from '../deviceProtocol.js'; -import { Type, Static } from '@sinclair/typebox'; -import JsonSchemaValidatorFactory from '../../../schemaValidation/JsonSchemaValidatorFactory.js'; -import JsonSchemaValidator from '../../../schemaValidation/JsonSchemaValidator.js'; +import type { DecodeResult, InferMessage, InferResponse, MessageWithResponse } from '../deviceProtocol.js'; +import type DeviceProtocol from '../deviceProtocol.js'; +import type { Static } from '@sinclair/typebox'; +import { Type } from '@sinclair/typebox'; +import type JsonSchemaValidatorFactory from '../../../schemaValidation/JsonSchemaValidatorFactory.js'; +import type JsonSchemaValidator from '../../../schemaValidation/JsonSchemaValidator.js'; import { parseAndValidateJson } from '../../../util/json.js'; import { normalizeError } from '../../../util/typeUtils.js'; @@ -44,6 +46,22 @@ export default class Zc95Protocol implements DeviceProtocol this.msgResponseValidator = jsonSchemaValidatorFactory.create(MsgResponseSchema); } + public static createMessage( + message: M, + responseType: ResponseToKey, + ): MsgAndResponseIdentifier { + const responseIdentifier: ResponseIdentifier = { + msgId: message.MsgId, + type: responseType, + }; + + return { + message, + responseType: undefined, + responseIdentifier, + }; + } + public encode(message: InferMessage): Buffer { return Buffer.from(JSON.stringify(message), 'utf-8'); } @@ -69,20 +87,4 @@ export default class Zc95Protocol implements DeviceProtocol return response.MsgId === messageResponse.responseIdentifier.msgId && response.Type === messageResponse.responseIdentifier.type; } - - public static createMessage( - message: M, - responseType: ResponseToKey, - ): MsgAndResponseIdentifier { - const responseIdentifier: ResponseIdentifier = { - msgId: message.MsgId, - type: responseType, - }; - - return { - message, - responseType: undefined, - responseIdentifier, - }; - } } diff --git a/src/device/protocol/zc95/zc95SerialDeviceProvider.ts b/src/device/protocol/zc95/zc95SerialDeviceProvider.ts index 0e551e8d..a3eb2e4d 100644 --- a/src/device/protocol/zc95/zc95SerialDeviceProvider.ts +++ b/src/device/protocol/zc95/zc95SerialDeviceProvider.ts @@ -1,23 +1,27 @@ -import { SerialPortStream } from '@serialport/stream'; -import Logger from '../../../logging/Logger.js'; -import SerialDeviceProvider, { SerialDeviceProviderPortOpenOptions } from '../../provider/serialDeviceProvider.js'; -import Zc95DeviceFactory from './zc95DeviceFactory.js'; +import type { SerialPortStream } from '@serialport/stream'; +import type Logger from '../../../logging/Logger.js'; +import type { SerialDeviceProviderPortOpenOptions } from '../../provider/serialDeviceProvider.js'; +import SerialDeviceProvider from '../../provider/serialDeviceProvider.js'; +import type Zc95DeviceFactory from './zc95DeviceFactory.js'; import Zc95Device from './zc95Device.js'; -import SerialPortFactory from '../../../factory/serialPortFactory.js'; +import type SerialPortFactory from '../../../factory/serialPortFactory.js'; import { FrameParser } from '../../../serial/frameParser.js'; import SynchronousSerialPort from '../../../serial/synchronousSerialPort.js'; import Zc95Protocol from './zc95Protocol.js'; import MessageResponseHandler from '../messageResponseHandler.js'; import Zc95MessageFactory from './zc95MessageFactory.js'; -import SerialDeviceTransportFactory from '../../transport/serialDeviceTransportFactory.js'; -import DeviceManager from '../../deviceManager.js'; -import SerialPortObserver, { SerialDeviceDetectionInfo } from '../../transport/serialPortObserver.js'; -import JsonSchemaValidatorFactory from '../../../schemaValidation/JsonSchemaValidatorFactory.js'; +import type SerialDeviceTransportFactory from '../../transport/serialDeviceTransportFactory.js'; +import type DeviceManager from '../../deviceManager.js'; +import type { SerialDeviceDetectionInfo } from '../../transport/serialPortObserver.js'; +import type SerialPortObserver from '../../transport/serialPortObserver.js'; +import type JsonSchemaValidatorFactory from '../../../schemaValidation/JsonSchemaValidatorFactory.js'; export default class Zc95SerialDeviceProvider extends SerialDeviceProvider { public static readonly providerName = 'zc95Serial'; + private static readonly EOT_SETTLE_DELAY_MS = 250; + private readonly transportFactory: SerialDeviceTransportFactory; private readonly deviceFactory: Zc95DeviceFactory; @@ -76,7 +80,7 @@ export default class Zc95SerialDeviceProvider extends SerialDeviceProvider extends DeviceProvider { + private static readonly DISCONNECT_TIMEOUT_MS = 2000; + private readonly bleObserver: BleObserver; protected constructor(deviceManager: DeviceManager, bleObserver: BleObserver, logger: Logger) { @@ -28,7 +32,7 @@ export default abstract class BleDeviceProvider extends return deviceDetectionInfo.type === 'ble'; } - protected override createDevice(deviceDetectionInfo: BleDeviceDetectionInfo): Promise { + protected override async createDevice(deviceDetectionInfo: BleDeviceDetectionInfo): Promise { return this.connectBleDevice(deviceDetectionInfo); } @@ -36,12 +40,14 @@ export default abstract class BleDeviceProvider extends await this.disconnectPeripheral(deviceDetectionInfo.peripheral); } + protected abstract connectBleDevice(deviceDetectionInfo: BleDeviceDetectionInfo): Promise; + private async disconnectPeripheral(peripheral: Peripheral): Promise { if (peripheral.state === 'connected') { try { await promiseWithTimeout( peripheral.disconnectAsync(), - 2000, + BleDeviceProvider.DISCONNECT_TIMEOUT_MS, `Timed out while disconnecting from device ${peripheral.id}`, ); } catch (e: unknown) { @@ -51,6 +57,4 @@ export default abstract class BleDeviceProvider extends peripheral.cancelConnect(); } } - - protected abstract connectBleDevice(deviceDetectionInfo: BleDeviceDetectionInfo): Promise; } diff --git a/src/device/provider/deviceProvider.ts b/src/device/provider/deviceProvider.ts index 3b39aa3a..9f279130 100644 --- a/src/device/provider/deviceProvider.ts +++ b/src/device/provider/deviceProvider.ts @@ -1,10 +1,13 @@ -import DeviceManager, { DeviceDetectionInfo, DeviceManagerEvent } from '../deviceManager.js'; +import type { DeviceDetectionInfo } from '../deviceManager.js'; +import type DeviceManager from '../deviceManager.js'; +import { DeviceManagerEvent } from '../deviceManager.js'; import DeviceOfferRejectedError from '../deviceOfferRejectedError.js'; -import Logger from '../../logging/Logger.js'; +import type Logger from '../../logging/Logger.js'; import { asyncHandler } from '../../util/async.js'; import { logError } from '../../util/error.js'; -import { AnyDevice, DeviceEvent } from '../device.js'; -import { DeviceId } from '../deviceId.js'; +import type { AnyDevice } from '../device.js'; +import { DeviceEvent } from '../device.js'; +import type { DeviceId } from '../deviceId.js'; import BaseError from 'modern-errors'; export type AnyDeviceProvider = DeviceProvider; @@ -72,6 +75,7 @@ export default abstract class DeviceProvider { // no-op default } @@ -79,6 +83,7 @@ export default abstract class DeviceProvider { // no-op default } @@ -95,6 +100,15 @@ export default abstract class DeviceProvider { + return Promise.resolve(); + } + + protected abstract canHandleDeviceDetectionInfo(deviceDetectionInfo: DeviceDetectionInfo): deviceDetectionInfo is DDI; + + protected abstract createDevice(deviceDetectionInfo: DDI): Promise; + private async handleDeviceDetection(deviceDetectionInfo: DeviceDetectionInfo): Promise { if (!this.canHandleDeviceDetectionInfo(deviceDetectionInfo)) { return; @@ -102,7 +116,7 @@ export default abstract class DeviceProvider this.createAndRegisterDevice(deviceDetectionInfo)); + const result = await this.deviceManager.offerDevice(deviceDetectionInfo, async () => this.createAndRegisterDevice(deviceDetectionInfo)); if (!result.successful) { if (result.reason instanceof DeviceOfferRejectedError) { @@ -140,13 +154,4 @@ export default abstract class DeviceProvider; - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - protected async onConnectFailed(deviceDetectionInfo: DDI): Promise { - return Promise.resolve(); - } } diff --git a/src/device/provider/deviceProviderFactory.ts b/src/device/provider/deviceProviderFactory.ts index cdcf25fa..60bfb316 100644 --- a/src/device/provider/deviceProviderFactory.ts +++ b/src/device/provider/deviceProviderFactory.ts @@ -1,5 +1,5 @@ -import { AnyDeviceProvider } from './deviceProvider.js'; -import { JsonObject } from '../../types.js'; +import type { AnyDeviceProvider } from './deviceProvider.js'; +import type { JsonObject } from '../../types.js'; type DeviceProviderFactory = { create(config: JsonObject): DP; diff --git a/src/device/provider/deviceProviderManager.ts b/src/device/provider/deviceProviderManager.ts index 61e7e87e..9a81d2b5 100644 --- a/src/device/provider/deviceProviderManager.ts +++ b/src/device/provider/deviceProviderManager.ts @@ -1,9 +1,9 @@ import { SequentialTaskQueue } from '@timesplinter/sequential-task-queue'; -import Settings from '../../settings/settings.js'; -import DeviceSource from '../../settings/deviceSource.js'; -import DeviceProviderFactory from './deviceProviderFactory.js'; -import Logger from '../../logging/Logger.js'; -import { AnyDeviceProvider } from './deviceProvider.js'; +import type Settings from '../../settings/settings.js'; +import type DeviceSource from '../../settings/deviceSource.js'; +import type DeviceProviderFactory from './deviceProviderFactory.js'; +import type Logger from '../../logging/Logger.js'; +import type { AnyDeviceProvider } from './deviceProvider.js'; import { logError } from '../../util/error.js'; type RunningProvider = { @@ -13,7 +13,7 @@ type RunningProvider = { export default class DeviceProviderManager { - private factories: Map>; + private readonly factories: Map>; private readonly logger: Logger; @@ -31,13 +31,13 @@ export default class DeviceProviderManager this.logger = logger.child({ name: DeviceProviderManager.name }); } - public loadFromSettings(settings: Settings): Promise + public async loadFromSettings(settings: Settings): Promise { - return this.enqueueOperation(() => this.doLoadFromSettings(settings)); + return this.enqueueOperation(async () => this.doLoadFromSettings(settings)); } - public stopProviders(): Promise { - return this.enqueueOperation(() => this.doStopProviders()); + public async stopProviders(): Promise { + return this.enqueueOperation(async () => this.doStopProviders()); } private async enqueueOperation(operation: () => Promise): Promise { @@ -113,10 +113,6 @@ export default class DeviceProviderManager })); } - private static fingerprintOf(deviceSource: DeviceSource): string { - return JSON.stringify({ type: deviceSource.type, config: deviceSource.config }); - } - private async doStopProviders(): Promise { const results = await Promise.allSettled([...this.providers.entries()].map(async ([id, runningProvider]) => { await runningProvider.provider.stop(); @@ -136,4 +132,8 @@ export default class DeviceProviderManager throw new Error(`Failed to stop ${errors.length} device provider(s)`); } } + + private static fingerprintOf(deviceSource: DeviceSource): string { + return JSON.stringify({ type: deviceSource.type, config: deviceSource.config }); + } } diff --git a/src/device/provider/genericDeviceProviderFactory.ts b/src/device/provider/genericDeviceProviderFactory.ts index 07d51720..d897668d 100644 --- a/src/device/provider/genericDeviceProviderFactory.ts +++ b/src/device/provider/genericDeviceProviderFactory.ts @@ -1,5 +1,5 @@ -import { AnyDeviceProvider } from './deviceProvider.js'; -import DeviceProviderFactory from './deviceProviderFactory.js'; +import type { AnyDeviceProvider } from './deviceProvider.js'; +import type DeviceProviderFactory from './deviceProviderFactory.js'; type TypedCtor = new (...args: never[]) => T; diff --git a/src/device/provider/serialDeviceProvider.ts b/src/device/provider/serialDeviceProvider.ts index 1354eae1..92d34a30 100644 --- a/src/device/provider/serialDeviceProvider.ts +++ b/src/device/provider/serialDeviceProvider.ts @@ -1,15 +1,17 @@ import DeviceProvider from './deviceProvider.js'; -import Logger from '../../logging/Logger.js'; -import { PortInfo } from '@serialport/bindings-interface'; -import { SerialPortOpenOptions } from 'serialport'; -import { SerialPortStream } from '@serialport/stream'; -import SerialPortFactory from '../../factory/serialPortFactory.js'; -import { AutoDetectTypes } from '@serialport/bindings-cpp'; +import type Logger from '../../logging/Logger.js'; +import type { PortInfo } from '@serialport/bindings-interface'; +import type { SerialPortOpenOptions } from 'serialport'; +import type { SerialPortStream } from '@serialport/stream'; +import type SerialPortFactory from '../../factory/serialPortFactory.js'; +import type { AutoDetectTypes } from '@serialport/bindings-cpp'; import BaseError from 'modern-errors'; -import DeviceManager, { DeviceDetectionInfo } from '../deviceManager.js'; +import type { DeviceDetectionInfo } from '../deviceManager.js'; +import type DeviceManager from '../deviceManager.js'; import { logError } from '../../util/error.js'; -import SerialPortObserver, { SerialDeviceDetectionInfo } from '../transport/serialPortObserver.js'; -import { AnyPeripheralDevice } from '../peripheralDevice.js'; +import type { SerialDeviceDetectionInfo } from '../transport/serialPortObserver.js'; +import type SerialPortObserver from '../transport/serialPortObserver.js'; +import type { AnyPeripheralDevice } from '../peripheralDevice.js'; export type SerialDeviceProviderPortOpenOptions = Omit, 'path' | 'autoOpen'>; @@ -85,8 +87,8 @@ export default abstract class SerialDeviceProvider { + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/class-methods-use-this + protected async preparePort(port: SerialPortStream, portInfo: PortInfo): Promise { return Promise.resolve(); } diff --git a/src/device/serializedTypes.ts b/src/device/serializedTypes.ts index e38d8628..a14ae492 100644 --- a/src/device/serializedTypes.ts +++ b/src/device/serializedTypes.ts @@ -1,6 +1,6 @@ -import DeviceState from './deviceState.js'; -import { DeviceAttributeModifier } from './attribute/deviceAttribute.js'; -import { DeviceId } from './deviceId.js'; +import type DeviceState from './deviceState.js'; +import type { DeviceAttributeModifier } from './attribute/deviceAttribute.js'; +import type { DeviceId } from './deviceId.js'; type SerializedDeviceAttributeBase = { name: string; diff --git a/src/device/transport/bleDeviceTransport.ts b/src/device/transport/bleDeviceTransport.ts index 5bffa19a..d4030b70 100644 --- a/src/device/transport/bleDeviceTransport.ts +++ b/src/device/transport/bleDeviceTransport.ts @@ -1,5 +1,5 @@ -import { Characteristic, Peripheral } from '@stoprocent/noble'; -import DeviceBidirectionalTransport from './deviceBidirectionalTransport.js'; +import type { Characteristic, Peripheral } from '@stoprocent/noble'; +import type DeviceBidirectionalTransport from './deviceBidirectionalTransport.js'; import { asyncHandler } from '../../util/async.js'; export default class BleUartDeviceTransport implements DeviceBidirectionalTransport @@ -14,23 +14,13 @@ export default class BleUartDeviceTransport implements DeviceBidirectionalTransp private isConnected = false; private isSubscribing = false; - private onCloseSubscribers: (() => void | Promise)[] = []; - private onReceiveSubscribers: ((data: Buffer) => void)[] = []; - private onConnectedSubscribers: (() => void)[] = []; + private readonly onCloseSubscribers: (() => void | Promise)[] = []; + private readonly onReceiveSubscribers: ((data: Buffer) => void)[] = []; + private readonly onConnectedSubscribers: (() => void)[] = []; private readonly connectHandler: (err: Error) => void; private readonly disconnectHandler: (err: Error) => void; - public static async create( - peripheral: Peripheral, - uartRxCharacteristicUuid: string, - uartTxCharacteristicUuid: string, - ): Promise { - const transport = new this(peripheral, uartRxCharacteristicUuid, uartTxCharacteristicUuid); - await transport.subscribe(); - return transport; - } - private constructor(peripheral: Peripheral, uartRxCharacteristicUuid: string, uartTxCharacteristicUuid: string) { this.peripheral = peripheral; this.uartRxCharacteristicUuid = uartRxCharacteristicUuid; @@ -51,6 +41,71 @@ export default class BleUartDeviceTransport implements DeviceBidirectionalTransp this.peripheral.on('disconnect', this.disconnectHandler); } + public static async create( + peripheral: Peripheral, + uartRxCharacteristicUuid: string, + uartTxCharacteristicUuid: string, + ): Promise { + const transport = new this(peripheral, uartRxCharacteristicUuid, uartTxCharacteristicUuid); + await transport.subscribe(); + return transport; + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/require-await + public async sendAndAwaitReceive(data: Buffer, timeout?: number): Promise { + throw new Error('Method not implemented.'); + } + + public async send(data: Buffer): Promise { + if (!this.isConnected || !this.rx) { + throw new Error('Transport not connected'); + } + await this.rx.writeAsync(data, false); + } + + public onReceive(dataProcessor: (data: Buffer) => void): void { + this.onReceiveSubscribers.push(dataProcessor); + this.tx?.on('data', dataProcessor); + } + + public onClose(callback: () => void | Promise): void { + this.onCloseSubscribers.push(callback); + } + + public onConnected(callback: () => void): void { + this.onConnectedSubscribers.push(callback); + } + + public isOpen(): boolean { + return this.isConnected; + } + + public async close(): Promise { + this.peripheral.off('connect', this.connectHandler); + this.peripheral.off('disconnect', this.disconnectHandler); + + if (this.tx) { + for (const subscriber of this.onReceiveSubscribers) { + this.tx.removeListener('data', subscriber); + } + try { + await this.tx.unsubscribeAsync(); + } catch { + // BLE manager may already be torn down on shutdown — ignore + } + } + + this.isConnected = false; + + for (const callback of this.onCloseSubscribers) { + await callback(); + } + } + + public getDeviceIdentifier(): string { + return this.peripheral.id; + } + private async subscribe(): Promise { if (this.isSubscribing || this.isConnected) { return; @@ -114,59 +169,4 @@ export default class BleUartDeviceTransport implements DeviceBidirectionalTransp this.isSubscribing = false; } } - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - public sendAndAwaitReceive(data: Buffer, timeout?: number): Promise { - throw new Error('Method not implemented.'); - } - - public async send(data: Buffer): Promise { - if (!this.isConnected || !this.rx) { - throw new Error('Transport not connected'); - } - await this.rx.writeAsync(data, false); - } - - public onReceive(dataProcessor: (data: Buffer) => void): void { - this.onReceiveSubscribers.push(dataProcessor); - this.tx?.on('data', dataProcessor); - } - - public onClose(callback: () => void | Promise): void { - this.onCloseSubscribers.push(callback); - } - - public onConnected(callback: () => void): void { - this.onConnectedSubscribers.push(callback); - } - - public isOpen(): boolean { - return this.isConnected; - } - - public async close(): Promise { - this.peripheral.off('connect', this.connectHandler); - this.peripheral.off('disconnect', this.disconnectHandler); - - if (this.tx) { - for (const subscriber of this.onReceiveSubscribers) { - this.tx.removeListener('data', subscriber); - } - try { - await this.tx.unsubscribeAsync(); - } catch { - // BLE manager may already be torn down on shutdown — ignore - } - } - - this.isConnected = false; - - for (const callback of this.onCloseSubscribers) { - await callback(); - } - } - - public getDeviceIdentifier(): string { - return this.peripheral.id; - } } diff --git a/src/device/transport/bleObserver.ts b/src/device/transport/bleObserver.ts index 4acb20ba..175f4fe9 100644 --- a/src/device/transport/bleObserver.ts +++ b/src/device/transport/bleObserver.ts @@ -1,9 +1,12 @@ -import noble, { Peripheral } from '@stoprocent/noble'; -import Logger from '../../logging/Logger.js'; -import DeviceManager, { DeviceDetectionInfo } from '../deviceManager.js'; +import type { Peripheral } from '@stoprocent/noble'; +import noble from '@stoprocent/noble'; +import type Logger from '../../logging/Logger.js'; +import type { DeviceDetectionInfo } from '../deviceManager.js'; +import type DeviceManager from '../deviceManager.js'; import { logError } from '../../util/error.js'; import { DetectionId } from '../deviceId.js'; import SharedObserver from './sharedObserver.js'; +import { MIN_AS_SECONDS, SECOND_AS_MILLISECONDS } from '../../util/numbers.js'; export type BleDeviceDetectionInfo = DeviceDetectionInfo & { type: 'ble'; @@ -15,7 +18,7 @@ export default class BleObserver extends SharedObserver private static readonly MIN_RSSI = -70; private static readonly UART_SERVICE_UUID = '6e400001b5a3f393e0a9e50e24dcca9e'; - private static readonly POWER_ON_WAIT_CHUNK_MS = 10 * 60 * 1000; // 10 minutes + private static readonly POWER_ON_WAIT_CHUNK_MS = 10 * MIN_AS_SECONDS * SECOND_AS_MILLISECONDS; // 10 minutes private readonly deviceManager: DeviceManager; @@ -35,7 +38,7 @@ export default class BleObserver extends SharedObserver this.deviceManager = deviceManager; } - protected onFirstStart(): Promise + protected async onFirstStart(): Promise { this.stopRequested = false; diff --git a/src/device/transport/deviceBidirectionalTransport.ts b/src/device/transport/deviceBidirectionalTransport.ts index 0d6d1c86..667fc41f 100644 --- a/src/device/transport/deviceBidirectionalTransport.ts +++ b/src/device/transport/deviceBidirectionalTransport.ts @@ -1,5 +1,5 @@ -import DeviceReadableTransport from './deviceReadableTransport.js'; -import DeviceWritableTransport from './deviceWritableTransport.js'; +import type DeviceReadableTransport from './deviceReadableTransport.js'; +import type DeviceWritableTransport from './deviceWritableTransport.js'; type DeviceTransport = { /** @@ -7,7 +7,7 @@ type DeviceTransport = { * @param data The command/input for the device * @param timeout The timeout the transport should wait for the operation to complete and get a response */ - sendAndAwaitReceive(data: Buffer, timeout?: number): Promise; + sendAndAwaitReceive: (data: Buffer, timeout?: number) => Promise; } & DeviceReadableTransport & DeviceWritableTransport; export default DeviceTransport; diff --git a/src/device/transport/deviceReadableTransport.ts b/src/device/transport/deviceReadableTransport.ts index bfeffafc..df7828fd 100644 --- a/src/device/transport/deviceReadableTransport.ts +++ b/src/device/transport/deviceReadableTransport.ts @@ -3,28 +3,28 @@ type DeviceReadableTransport = { * When data is received * @param dataProcessor */ - onReceive(dataProcessor: (data: Buffer) => void): void; + onReceive: (dataProcessor: (data: Buffer) => void) => void; /** * Called when the transport is closed (for example when a serial device is unplugged) * @param callback */ - onClose(callback: () => void | Promise): void; + onClose: (callback: () => void | Promise) => void; /** * Returns whether the transport is currently open/active or not */ - isOpen(): boolean; + isOpen: () => boolean; /** * Closes the transport */ - close(): Promise; + close: () => Promise; /** * Returns a deterministic and unique device identifier (for example hardware serial number) */ - getDeviceIdentifier(): string; + getDeviceIdentifier: () => string; }; export default DeviceReadableTransport; diff --git a/src/device/transport/serialDeviceTransport.ts b/src/device/transport/serialDeviceTransport.ts index 00b156d6..837d142d 100644 --- a/src/device/transport/serialDeviceTransport.ts +++ b/src/device/transport/serialDeviceTransport.ts @@ -1,5 +1,5 @@ -import DeviceBidirectionalTransport from './deviceBidirectionalTransport.js'; -import SynchronousSerialPort from '../../serial/synchronousSerialPort.js'; +import type DeviceBidirectionalTransport from './deviceBidirectionalTransport.js'; +import type SynchronousSerialPort from '../../serial/synchronousSerialPort.js'; export default class SerialDeviceTransport implements DeviceBidirectionalTransport { diff --git a/src/device/transport/serialDeviceTransportFactory.ts b/src/device/transport/serialDeviceTransportFactory.ts index fefad89d..4ed7cd6f 100644 --- a/src/device/transport/serialDeviceTransportFactory.ts +++ b/src/device/transport/serialDeviceTransportFactory.ts @@ -1,8 +1,9 @@ -import SynchronousSerialPort from '../../serial/synchronousSerialPort.js'; +import type SynchronousSerialPort from '../../serial/synchronousSerialPort.js'; import SerialDeviceTransport from './serialDeviceTransport.js'; export default class SerialDeviceTransportFactory { + // eslint-disable-next-line @typescript-eslint/class-methods-use-this public create(serialPort: SynchronousSerialPort, frameStartBytes?: Buffer, frameEndBytes?: Buffer): SerialDeviceTransport { return new SerialDeviceTransport(serialPort, frameStartBytes, frameEndBytes); } diff --git a/src/device/transport/serialPortObserver.ts b/src/device/transport/serialPortObserver.ts index d6de2931..61574a73 100644 --- a/src/device/transport/serialPortObserver.ts +++ b/src/device/transport/serialPortObserver.ts @@ -1,12 +1,14 @@ import { SerialPort } from 'serialport'; -import { PortInfo } from '@serialport/bindings-interface'; -import Logger from '../../logging/Logger.js'; -import DeviceManager, { DeviceDetectionInfo } from '../deviceManager.js'; +import type { PortInfo } from '@serialport/bindings-interface'; +import type Logger from '../../logging/Logger.js'; +import type { DeviceDetectionInfo } from '../deviceManager.js'; +import type DeviceManager from '../deviceManager.js'; import { usb } from 'usb'; import { logError } from '../../util/error.js'; import { DetectionId } from '../deviceId.js'; import SharedObserver from './sharedObserver.js'; -import { CancellationToken, cancellationTokenReasons } from '@timesplinter/sequential-task-queue'; +import type { CancellationToken } from '@timesplinter/sequential-task-queue'; +import { cancellationTokenReasons } from '@timesplinter/sequential-task-queue'; import LatestOnlyTaskQueue from '../../util/latestOnlyTaskQueue.js'; export type SerialDeviceDetectionInfo = DeviceDetectionInfo & { @@ -16,9 +18,11 @@ export type SerialDeviceDetectionInfo = DeviceDetectionInfo & { export default class SerialPortObserver extends SharedObserver { + private static readonly DELAY_AFTER_USB_EVENT_MS = 1000; + protected readonly deviceManager: DeviceManager; - private managedDevices = new Map(); + private readonly managedDevices = new Map(); private onUsbEventRef?: () => void; @@ -34,52 +38,6 @@ export default class SerialPortObserver extends SharedObserver this.deviceManager = deviceManager; } - protected async onFirstStart(): Promise - { - await this.discoverSerialDevices(); - - this.onUsbEventRef = (): void => { - if (this.rescanTimer === undefined) { - this.logger.debug('USB event detected, scanning for serial devices in 1s...'); - } else { - clearTimeout(this.rescanTimer); - } - - this.rescanTimer = setTimeout(() => { - this.rescanTimer = undefined; - - this.discoveryQueue.run(cancellationToken => this.discoverSerialDevices(cancellationToken)) - .catch((e: unknown) => { - if (e === cancellationTokenReasons.cancel) { - this.logger.debug('Serial device discovery run cancelled (superseded by a later run or stopped)'); - } else { - logError(this.logger, 'Error while scanning for new serial devices', e); - } - }); - }, 1000); - }; - - usb.addEventListener('connect', this.onUsbEventRef); - usb.addEventListener('disconnect', this.onUsbEventRef); - } - - /** - * Gives a provider that just joined an already-running observer a chance at devices detected - * before it subscribed - discoverSerialDevices() itself only announces newly-discovered - * ports, so a device already sitting unclaimed here (e.g. because no provider wanted it yet) - * would otherwise never be offered to this provider. - */ - protected override onSubsequentStart(): Promise - { - // announceDetectedDevice() itself is a no-op for a device that's already connected, so - // there's no need to filter those out here first. - for (const deviceInfo of this.managedDevices.values()) { - this.deviceManager.announceDetectedDevice(deviceInfo); - } - - return Promise.resolve(); - } - public async discoverSerialDevices(cancellationToken?: CancellationToken): Promise { const foundDevices = new Map(); @@ -132,6 +90,52 @@ export default class SerialPortObserver extends SharedObserver } } + protected async onFirstStart(): Promise + { + await this.discoverSerialDevices(); + + this.onUsbEventRef = (): void => { + if (this.rescanTimer === undefined) { + this.logger.debug('USB event detected, scanning for serial devices in 1s...'); + } else { + clearTimeout(this.rescanTimer); + } + + this.rescanTimer = setTimeout(() => { + this.rescanTimer = undefined; + + this.discoveryQueue.run(async cancellationToken => this.discoverSerialDevices(cancellationToken)) + .catch((e: unknown) => { + if (e === cancellationTokenReasons.cancel) { + this.logger.debug('Serial device discovery run cancelled (superseded by a later run or stopped)'); + } else { + logError(this.logger, 'Error while scanning for new serial devices', e); + } + }); + }, SerialPortObserver.DELAY_AFTER_USB_EVENT_MS); + }; + + usb.addEventListener('connect', this.onUsbEventRef); + usb.addEventListener('disconnect', this.onUsbEventRef); + } + + /** + * Gives a provider that just joined an already-running observer a chance at devices detected + * before it subscribed - discoverSerialDevices() itself only announces newly-discovered + * ports, so a device already sitting unclaimed here (e.g. because no provider wanted it yet) + * would otherwise never be offered to this provider. + */ + protected override async onSubsequentStart(): Promise + { + // announceDetectedDevice() itself is a no-op for a device that's already connected, so + // there's no need to filter those out here first. + for (const deviceInfo of this.managedDevices.values()) { + this.deviceManager.announceDetectedDevice(deviceInfo); + } + + return Promise.resolve(); + } + protected async onLastStop(): Promise { if (this.rescanTimer !== undefined) { clearTimeout(this.rescanTimer); diff --git a/src/device/transport/sharedObserver.ts b/src/device/transport/sharedObserver.ts index 490a2ef9..dcaf2e9c 100644 --- a/src/device/transport/sharedObserver.ts +++ b/src/device/transport/sharedObserver.ts @@ -1,4 +1,4 @@ -import Logger from '../../logging/Logger.js'; +import type Logger from '../../logging/Logger.js'; export default abstract class SharedObserver { @@ -55,6 +55,16 @@ export default abstract class SharedObserver await this.onLastStop(); } + /** + * Runs for every caller that joins an already-running observer (i.e. every start() call + * except the first). No-op by default - only relevant to observers with multiple consumers + * per instance, where a newly-joining consumer may need to catch up on state it missed. + */ + // eslint-disable-next-line @typescript-eslint/class-methods-use-this + protected async onSubsequentStart(): Promise { + return Promise.resolve(); + } + /** * Runs once, when the first caller acquires this observer. */ @@ -64,13 +74,4 @@ export default abstract class SharedObserver * Runs once, when the last remaining caller releases this observer. */ protected abstract onLastStop(): Promise; - - /** - * Runs for every caller that joins an already-running observer (i.e. every start() call - * except the first). No-op by default - only relevant to observers with multiple consumers - * per instance, where a newly-joining consumer may need to catch up on state it missed. - */ - protected async onSubsequentStart(): Promise { - return Promise.resolve(); - } } diff --git a/src/device/updater/abstractDeviceUpdater.ts b/src/device/updater/abstractDeviceUpdater.ts index a41bb352..7c8c25a0 100644 --- a/src/device/updater/abstractDeviceUpdater.ts +++ b/src/device/updater/abstractDeviceUpdater.ts @@ -1,6 +1,6 @@ -import PlainToClassSerializer from '../../serialization/plainToClassSerializer.js'; -import DeviceUpdaterInterface from './deviceUpdaterInterface.js'; -import { AnyDevice, DeviceData } from '../device.js'; +import type PlainToClassSerializer from '../../serialization/plainToClassSerializer.js'; +import type DeviceUpdaterInterface from './deviceUpdaterInterface.js'; +import type { AnyDevice, DeviceData } from '../device.js'; export default abstract class AbstractDeviceUpdater implements DeviceUpdaterInterface { diff --git a/src/device/updater/bufferedDeviceUpdater.ts b/src/device/updater/bufferedDeviceUpdater.ts index b219b2f3..030e748a 100644 --- a/src/device/updater/bufferedDeviceUpdater.ts +++ b/src/device/updater/bufferedDeviceUpdater.ts @@ -1,13 +1,9 @@ -import { AnyDevice, DeviceData } from '../device.js'; -import DeviceUpdaterInterface from './deviceUpdaterInterface.js'; +import type { AnyDevice, DeviceData } from '../device.js'; +import type DeviceUpdaterInterface from './deviceUpdaterInterface.js'; import { SequentialTaskQueue } from '@timesplinter/sequential-task-queue'; export default class BufferedDeviceUpdater implements DeviceUpdaterInterface { - private readonly decoratedDeviceUpdater: DeviceUpdaterInterface; - - private readonly queue: SequentialTaskQueue; - private static readonly handleUpdate = async ( deviceUpdater: DeviceUpdaterInterface, device: AnyDevice, @@ -16,6 +12,10 @@ export default class BufferedDeviceUpdater implements DeviceUpdaterInterface await deviceUpdater.update(device, deviceData); }; + private readonly decoratedDeviceUpdater: DeviceUpdaterInterface; + + private readonly queue: SequentialTaskQueue; + public constructor(decoratedDeviceUpdater: DeviceUpdaterInterface) { this.decoratedDeviceUpdater = decoratedDeviceUpdater; this.queue = new SequentialTaskQueue(); diff --git a/src/device/updater/deviceUpdaterInterface.ts b/src/device/updater/deviceUpdaterInterface.ts index bc0617db..7d001c8f 100644 --- a/src/device/updater/deviceUpdaterInterface.ts +++ b/src/device/updater/deviceUpdaterInterface.ts @@ -1,7 +1,7 @@ -import { AnyDevice, DeviceData } from '../device.js'; +import type { AnyDevice, DeviceData } from '../device.js'; type DeviceUpdaterInterface = { - update(device: AnyDevice, rawData: DeviceData): Promise; + update: (device: AnyDevice, rawData: DeviceData) => Promise; }; export default DeviceUpdaterInterface; diff --git a/src/entity/deviceList.ts b/src/entity/deviceList.ts index 3081cb4e..fc04028b 100644 --- a/src/entity/deviceList.ts +++ b/src/entity/deviceList.ts @@ -1,5 +1,6 @@ import { Exclude, Type } from 'class-transformer'; -import Device, { AnyDevice } from '../device/device.js'; +import type { AnyDevice } from '../device/device.js'; +import Device from '../device/device.js'; import List from './list.js'; import deviceDiscriminator from '../serialization/discriminator/deviceDiscriminator.js'; diff --git a/src/entity/list.ts b/src/entity/list.ts index be061797..4d0bce4b 100644 --- a/src/entity/list.ts +++ b/src/entity/list.ts @@ -4,10 +4,10 @@ import { Exclude, Expose } from 'class-transformer'; export default class List { @Expose() - private readonly count: number; + protected readonly items: T[]; @Expose() - protected readonly items: T[]; + private readonly count: number; public constructor(items: T[]) { diff --git a/src/env.ts b/src/env.ts index 7e9c26ac..d7deaf31 100644 --- a/src/env.ts +++ b/src/env.ts @@ -1,5 +1,6 @@ import { Value } from '@sinclair/typebox/value'; -import { Static, Type } from '@sinclair/typebox'; +import type { Static } from '@sinclair/typebox'; +import { Type } from '@sinclair/typebox'; import os from 'os'; const EnvSchema = Type.Object({ diff --git a/src/factory/dateFactory.ts b/src/factory/dateFactory.ts index 69d087db..99e6f5db 100644 --- a/src/factory/dateFactory.ts +++ b/src/factory/dateFactory.ts @@ -1,5 +1,6 @@ export default class DateFactory { + // eslint-disable-next-line @typescript-eslint/class-methods-use-this public now(): Date { return new Date(); diff --git a/src/factory/eventEmitterFactory.ts b/src/factory/eventEmitterFactory.ts index e3730dc1..cc7713a6 100644 --- a/src/factory/eventEmitterFactory.ts +++ b/src/factory/eventEmitterFactory.ts @@ -2,6 +2,7 @@ import { EventEmitter } from 'events'; export default class EventEmitterFactory { + // eslint-disable-next-line @typescript-eslint/class-methods-use-this public create(): EventEmitter { return new EventEmitter(); } diff --git a/src/factory/serialPortFactory.ts b/src/factory/serialPortFactory.ts index 919ac263..21c2be41 100644 --- a/src/factory/serialPortFactory.ts +++ b/src/factory/serialPortFactory.ts @@ -1,9 +1,11 @@ -import { SerialPort, SerialPortOpenOptions } from 'serialport'; -import { SerialPortStream } from '@serialport/stream'; -import { AutoDetectTypes } from '@serialport/bindings-cpp'; +import type { SerialPortOpenOptions } from 'serialport'; +import { SerialPort } from 'serialport'; +import type { SerialPortStream } from '@serialport/stream'; +import type { AutoDetectTypes } from '@serialport/bindings-cpp'; export default class SerialPortFactory { + // eslint-disable-next-line @typescript-eslint/class-methods-use-this public create(options: SerialPortOpenOptions): SerialPortStream { return new SerialPort(options); } diff --git a/src/health/healthMetricsCollector.ts b/src/health/healthMetricsCollector.ts index b307f05c..ad236c9b 100644 --- a/src/health/healthMetricsCollector.ts +++ b/src/health/healthMetricsCollector.ts @@ -1,11 +1,13 @@ import os from 'os'; import process from 'process'; -import { EventEmitter } from 'events'; +import type { EventEmitter } from 'events'; import { OSUtils } from 'node-os-utils'; -import { IntervalAsync, setIntervalAsync } from '../util/async.js'; -import Logger from '../logging/Logger.js'; +import type { IntervalAsync } from '../util/async.js'; +import { setIntervalAsync } from '../util/async.js'; +import type Logger from '../logging/Logger.js'; import { logError } from '../util/error.js'; -import { SerializedHealthMetrics } from './serializedTypes.js'; +import type { SerializedHealthMetrics } from './serializedTypes.js'; +import { SECOND_AS_MILLISECONDS } from '../util/numbers.js'; export enum HealthMetricsCollectorEvent { collected = 'healthMetricsCollected', @@ -13,6 +15,10 @@ export enum HealthMetricsCollectorEvent { export default class HealthMetricsCollector { + private static readonly PERCENTAGE_MAX = 100; + + private static readonly COLLECTION_TIMEOUT_MULTIPLIER = 3; + private readonly osUtils: OSUtils; private readonly logger: Logger; @@ -40,10 +46,10 @@ export default class HealthMetricsCollector } this.intervalHandle = setIntervalAsync( - async () => await this.refresh(), + async () => { await this.refresh() }, { intervalMs, - timeoutMs: intervalMs * 3, + timeoutMs: intervalMs * HealthMetricsCollector.COLLECTION_TIMEOUT_MULTIPLIER, onError: err => logError(this.logger, `Health metrics refresh failed`, err), }, ); @@ -96,7 +102,7 @@ export default class HealthMetricsCollector usedMemMb: memInfo.data.used.toMB(), freeMemMb: memInfo.data.available.toMB(), usedMemPercentage: memInfo.data.usagePercentage, - freeMemPercentage: 100 - memInfo.data.usagePercentage, + freeMemPercentage: HealthMetricsCollector.PERCENTAGE_MAX - memInfo.data.usagePercentage, } : null, os: { @@ -114,7 +120,7 @@ export default class HealthMetricsCollector ?.addresses.find(a => a.family === 'IPv4' && !a.internal)?.address ?? null) : null, hostname: os.hostname(), - uptime: sysUptime.success ? Math.floor(sysUptime.data.uptime / 1000) : null, + uptime: sysUptime.success ? Math.floor(sysUptime.data.uptime / SECOND_AS_MILLISECONDS) : null, }, }; diff --git a/src/health/serializedTypes.ts b/src/health/serializedTypes.ts index b5ae6413..744a1b25 100644 --- a/src/health/serializedTypes.ts +++ b/src/health/serializedTypes.ts @@ -1,4 +1,4 @@ -import { NetworkStats } from 'node-os-utils'; +import type { NetworkStats } from 'node-os-utils'; export type SerializedHealthMetrics = { process: { diff --git a/src/index.ts b/src/index.ts index 5f6ee6f9..05a07429 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import 'dotenv/config'; import 'reflect-metadata'; -import { createApp, createContainer, SslConfig } from './app.js'; +import type { SslConfig } from './app.js'; +import { createApp, createContainer } from './app.js'; import { parseEnv } from './env.js'; import { logError } from './util/error.js'; diff --git a/src/logging/Logger.ts b/src/logging/Logger.ts index 48010c1e..4b830173 100644 --- a/src/logging/Logger.ts +++ b/src/logging/Logger.ts @@ -2,14 +2,14 @@ export type ChildLoggerBindings = { name?: string }; export type ChildLoggerOptions = { level?: string }; type Logger = { - child(bindings?: ChildLoggerBindings, options?: ChildLoggerOptions): Logger; + child: (bindings?: ChildLoggerBindings, options?: ChildLoggerOptions) => Logger; - trace(msg: string, context?: unknown): void; - debug(msg: string, context?: unknown): void; - info(msg: string, context?: unknown): void; - warn(msg: string, context?: unknown): void; - error(msg: string, context?: unknown): void; - fatal(msg: string, context?: unknown): void; + trace: (msg: string, context?: unknown) => void; + debug: (msg: string, context?: unknown) => void; + info: (msg: string, context?: unknown) => void; + warn: (msg: string, context?: unknown) => void; + error: (msg: string, context?: unknown) => void; + fatal: (msg: string, context?: unknown) => void; }; export default Logger; diff --git a/src/logging/PinoLogger.ts b/src/logging/PinoLogger.ts index a3c921fe..bbace725 100644 --- a/src/logging/PinoLogger.ts +++ b/src/logging/PinoLogger.ts @@ -1,9 +1,10 @@ -import Logger, { ChildLoggerBindings, ChildLoggerOptions } from './Logger.js'; -import pino from 'pino/pino.js'; +import type { ChildLoggerBindings, ChildLoggerOptions } from './Logger.js'; +import type Logger from './Logger.js'; +import type pino from 'pino/pino.js'; export default class PinoLogger implements Logger { - private pino: pino.Logger; + private readonly pino: pino.Logger; public constructor(logger: pino.Logger) { this.pino = logger; diff --git a/src/middleware/contentTypeMiddleware.ts b/src/middleware/contentTypeMiddleware.ts index 26d02468..426fc195 100644 --- a/src/middleware/contentTypeMiddleware.ts +++ b/src/middleware/contentTypeMiddleware.ts @@ -1,4 +1,4 @@ -import { Request, Response, NextFunction } from 'express'; +import type { Request, Response, NextFunction } from 'express'; export default (req: Request, res: Response, next: NextFunction): void => { // TODO let's check if we will need this diff --git a/src/repository/automationScriptRepository.ts b/src/repository/automationScriptRepository.ts index a0567a38..25b94580 100644 --- a/src/repository/automationScriptRepository.ts +++ b/src/repository/automationScriptRepository.ts @@ -1,7 +1,7 @@ import path from 'path'; import AutomationScript from '../entity/automationScript.js'; import { hasProperty } from '../util/objects.js'; -import AutomationScriptRepositoryInterface from './automationScriptRepositoryInterface.js'; +import type AutomationScriptRepositoryInterface from './automationScriptRepositoryInterface.js'; import fs from 'fs'; export default class AutomationScriptRepository implements AutomationScriptRepositoryInterface diff --git a/src/repository/automationScriptRepositoryInterface.ts b/src/repository/automationScriptRepositoryInterface.ts index f89bccbc..98fd7014 100644 --- a/src/repository/automationScriptRepositoryInterface.ts +++ b/src/repository/automationScriptRepositoryInterface.ts @@ -1,13 +1,13 @@ -import AutomationScript from '../entity/automationScript.js'; +import type AutomationScript from '../entity/automationScript.js'; type AutomationScriptRepositoryInterface = { - getAll(): AutomationScript[]; + getAll: () => AutomationScript[]; - getByName(name: string): string | null; + getByName: (name: string) => string | null; - save(fileName: string, data: string): void; + save: (fileName: string, data: string) => void; - delete(fileName: string): void; + delete: (fileName: string) => void; }; export default AutomationScriptRepositoryInterface; diff --git a/src/repository/connectedDeviceRepository.ts b/src/repository/connectedDeviceRepository.ts index 285d05e6..77814ed4 100644 --- a/src/repository/connectedDeviceRepository.ts +++ b/src/repository/connectedDeviceRepository.ts @@ -1,7 +1,7 @@ -import DeviceRepositoryInterface from './deviceRepositoryInterface.js'; -import DeviceManager from '../device/deviceManager.js'; -import { AnyDevice } from '../device/device.js'; -import { DeviceId } from '../device/deviceId.js'; +import type DeviceRepositoryInterface from './deviceRepositoryInterface.js'; +import type DeviceManager from '../device/deviceManager.js'; +import type { AnyDevice } from '../device/device.js'; +import type { DeviceId } from '../device/deviceId.js'; export default class ConnectedDeviceRepository implements DeviceRepositoryInterface { diff --git a/src/repository/deviceRepositoryInterface.ts b/src/repository/deviceRepositoryInterface.ts index 5018aee0..34042d10 100644 --- a/src/repository/deviceRepositoryInterface.ts +++ b/src/repository/deviceRepositoryInterface.ts @@ -1,9 +1,10 @@ -import { AnyDevice } from '../device/device.js'; +import type { AnyDevice } from '../device/device.js'; +import type { DeviceId } from '../device/deviceId.js'; type DeviceRepositoryInterface = { - getAll(): AnyDevice[]; + getAll: () => AnyDevice[]; - getById(uuid: string): AnyDevice | null; + getById: (uuid: DeviceId) => AnyDevice | null; }; export default DeviceRepositoryInterface; diff --git a/src/schemaValidation/JsonSchemaValidator.ts b/src/schemaValidation/JsonSchemaValidator.ts index f23dc15b..bd193935 100644 --- a/src/schemaValidation/JsonSchemaValidator.ts +++ b/src/schemaValidation/JsonSchemaValidator.ts @@ -1,5 +1,5 @@ -import { Ajv, ErrorObject, ValidateFunction } from 'ajv'; -import { Static, TSchema } from '@sinclair/typebox'; +import type { Ajv, ErrorObject, ValidateFunction } from 'ajv'; +import type { Static, TSchema } from '@sinclair/typebox'; export default class JsonSchemaValidator { diff --git a/src/schemaValidation/JsonSchemaValidatorFactory.ts b/src/schemaValidation/JsonSchemaValidatorFactory.ts index 6bb9a74e..0be3e243 100644 --- a/src/schemaValidation/JsonSchemaValidatorFactory.ts +++ b/src/schemaValidation/JsonSchemaValidatorFactory.ts @@ -1,6 +1,6 @@ -import { Ajv } from 'ajv'; +import type { Ajv } from 'ajv'; import JsonSchemaValidator from './JsonSchemaValidator.js'; -import { TSchema } from '@sinclair/typebox'; +import type { TSchema } from '@sinclair/typebox'; export default class JsonSchemaValidatorFactory { diff --git a/src/schemaValidation/schemaValidationError.ts b/src/schemaValidation/schemaValidationError.ts index 31a41f1e..fa971eeb 100644 --- a/src/schemaValidation/schemaValidationError.ts +++ b/src/schemaValidation/schemaValidationError.ts @@ -1,4 +1,4 @@ -import { ErrorObject } from 'ajv'; +import type { ErrorObject } from 'ajv'; export default class SchemaValidationError extends Error { diff --git a/src/serial/frameParser.ts b/src/serial/frameParser.ts index 9ff924a0..63eccb03 100644 --- a/src/serial/frameParser.ts +++ b/src/serial/frameParser.ts @@ -1,4 +1,5 @@ -import { Transform, TransformCallback, TransformOptions } from 'stream'; +import type { TransformCallback, TransformOptions } from 'stream'; +import { Transform } from 'stream'; export type FrameParserOptions = { stx: number; @@ -10,6 +11,8 @@ type State = 'IDLE' | 'RECV'; export class FrameParser extends Transform { + private static readonly DEFAULT_MAX_MESSAGE_SIZE = 4096; + private readonly stx: number; private readonly etx: number; private readonly maxMessageSize: number; @@ -22,7 +25,7 @@ export class FrameParser extends Transform this.stx = options.stx; this.etx = options.etx; - this.maxMessageSize = options.maxMessageSize ?? 4096; + this.maxMessageSize = options.maxMessageSize ?? FrameParser.DEFAULT_MAX_MESSAGE_SIZE; } public override _transform(chunk: Buffer, _: BufferEncoding, callback: TransformCallback): void { diff --git a/src/serial/synchronousSerialPort.ts b/src/serial/synchronousSerialPort.ts index 97468bb8..5cf15886 100644 --- a/src/serial/synchronousSerialPort.ts +++ b/src/serial/synchronousSerialPort.ts @@ -1,17 +1,18 @@ -import { Readable } from 'stream'; -import { SerialPortStream } from '@serialport/stream'; -import { cancellationTokenReasons, SequentialTaskQueue, TaskOptions } from '@timesplinter/sequential-task-queue'; -import { PortInfo } from '@serialport/bindings-interface'; -import Logger from '../logging/Logger.js'; +import type { Readable } from 'stream'; +import type { SerialPortStream } from '@serialport/stream'; +import type { TaskOptions } from '@timesplinter/sequential-task-queue'; +import { cancellationTokenReasons, SequentialTaskQueue } from '@timesplinter/sequential-task-queue'; +import type { PortInfo } from '@serialport/bindings-interface'; +import type Logger from '../logging/Logger.js'; import { logError } from '../util/error.js'; type CloseHandler = () => void | Promise; export default class SynchronousSerialPort { - private reader: Readable; + private readonly reader: Readable; - private writer: SerialPortStream; + private readonly writer: SerialPortStream; private readonly portInfo: PortInfo; @@ -42,7 +43,7 @@ export default class SynchronousSerialPort } public async write(data: Buffer): Promise { - return this.queue.push(() => new Promise((resolve, reject) => { + return this.queue.push(async () => new Promise((resolve, reject) => { this.writer.write(data, (err: Error | null | undefined) => (err) ? reject(err) : resolve()); })); } @@ -66,36 +67,7 @@ export default class SynchronousSerialPort this.closePromise = this.doClose(); - await this.closePromise; - } - - private async doClose(): Promise { - this.closed = true; - void this.queue.close(true); - this.writer.off('close', this.handleStreamClose); - this.reader.off('close', this.handleStreamClose); - - if (this.writer.isOpen) { - await new Promise(resolve => { - this.writer.close(err => { - if (err) { - logError(this.logger, `Error while closing serial port '${this.portInfo.path}'`, err); - } - resolve(); - }); - }); - } - - this.writer.destroy(); - this.reader.destroy(); - - for (const subscriber of this.closeSubscribers) { - try { - await subscriber(); - } catch (e: unknown) { - logError(this.logger, 'Error in writer/reader close handler', e); - } - } + return this.closePromise; } public async writeAndExpect(data: Buffer, timeoutMs = 1000): Promise { @@ -104,7 +76,7 @@ export default class SynchronousSerialPort // Very important to wrap the promise in a function: () => new Promise(...). // If not, it's immediately executed! - const wrappedPromise = (): Promise => new Promise((resolve, reject) => { + const wrappedPromise = async (): Promise => new Promise((resolve, reject) => { const errorHandler = (err: Error): void => { removeListeners(); reject(err); @@ -157,4 +129,33 @@ export default class SynchronousSerialPort public getPortInfo(): PortInfo { return this.portInfo; } + + private async doClose(): Promise { + this.closed = true; + void this.queue.close(true); + this.writer.off('close', this.handleStreamClose); + this.reader.off('close', this.handleStreamClose); + + if (this.writer.isOpen) { + await new Promise(resolve => { + this.writer.close(err => { + if (err) { + logError(this.logger, `Error while closing serial port '${this.portInfo.path}'`, err); + } + resolve(); + }); + }); + } + + this.writer.destroy(); + this.reader.destroy(); + + for (const subscriber of this.closeSubscribers) { + try { + await subscriber(); + } catch (e: unknown) { + logError(this.logger, 'Error in writer/reader close handler', e); + } + } + } } diff --git a/src/serialization/classToPlainSerializer.ts b/src/serialization/classToPlainSerializer.ts index 19ccbed4..eeff0ea4 100644 --- a/src/serialization/classToPlainSerializer.ts +++ b/src/serialization/classToPlainSerializer.ts @@ -1,4 +1,5 @@ -import { instanceToPlain, ClassTransformOptions, TypeOptions } from 'class-transformer'; +import type { ClassTransformOptions, TypeOptions } from 'class-transformer'; +import { instanceToPlain } from 'class-transformer'; export default class ClassToPlainSerializer { diff --git a/src/serialization/discriminator/objectDiscriminator.ts b/src/serialization/discriminator/objectDiscriminator.ts index a67d62a2..552e5720 100644 --- a/src/serialization/discriminator/objectDiscriminator.ts +++ b/src/serialization/discriminator/objectDiscriminator.ts @@ -1,4 +1,4 @@ -import { ClassConstructor, TypeOptions } from 'class-transformer'; +import type { ClassConstructor, TypeOptions } from 'class-transformer'; type DiscriminatorMap = { // Name of the type. diff --git a/src/serialization/plainToClassSerializer.ts b/src/serialization/plainToClassSerializer.ts index ac7d6f1e..5a06ec9e 100644 --- a/src/serialization/plainToClassSerializer.ts +++ b/src/serialization/plainToClassSerializer.ts @@ -1,7 +1,8 @@ -import { ClassConstructor, ClassTransformOptions, plainToInstance } from 'class-transformer'; -import { TSchema } from '@sinclair/typebox'; +import type { ClassConstructor, ClassTransformOptions } from 'class-transformer'; +import { plainToInstance } from 'class-transformer'; +import type { TSchema } from '@sinclair/typebox'; import { Value } from '@sinclair/typebox/value'; -import { Ajv } from 'ajv'; +import type { Ajv } from 'ajv'; import SchemaValidationError from '../schemaValidation/schemaValidationError.js'; export default class PlainToClassSerializer diff --git a/src/serviceMap.ts b/src/serviceMap.ts index 9424d7a3..11f37aae 100644 --- a/src/serviceMap.ts +++ b/src/serviceMap.ts @@ -1,57 +1,57 @@ -import { Ajv } from 'ajv'; -import ClassToPlainSerializer from './serialization/classToPlainSerializer.js'; -import PlainToClassSerializer from './serialization/plainToClassSerializer.js'; -import Logger from './logging/Logger.js'; -import DeviceManager from './device/deviceManager.js'; -import SerialDeviceTransportFactory from './device/transport/serialDeviceTransportFactory.js'; -import DeviceProviderFactory from './device/provider/deviceProviderFactory.js'; -import SlvCtrlPlusDeviceFactory from './device/protocol/slvCtrlPlus/slvCtrlPlusDeviceFactory.js'; -import ButtplugIoDeviceFactory from './device/protocol/buttplugIo/buttplugIoDeviceFactory.js'; -import DeviceNameGenerator from './device/deviceNameGenerator.js'; -import DeviceUpdaterInterface from './device/updater/deviceUpdaterInterface.js'; -import UuidFactory from './factory/uuidFactory.js'; -import DateFactory from './factory/dateFactory.js'; -import Settings from './settings/settings.js'; -import SettingsManager from './settings/settingsManager.js'; -import ScriptRuntime from './automation/scriptRuntime.js'; -import ScriptVmFactory from './automation/scriptVmFactory.js'; -import ConnectedDeviceRepository from './repository/connectedDeviceRepository.js'; -import AutomationScriptRepository from './repository/automationScriptRepository.js'; -import DeviceProviderManager from './device/provider/deviceProviderManager.js'; -import DeviceUpdateHandler from './socket/deviceUpdateHandler.js'; -import HealthController from './controller/healthController.js'; -import HealthMetricsCollector from './health/healthMetricsCollector.js'; -import GetDevicesController from './controller/getDevicesController.js'; -import GetDeviceController from './controller/getDeviceController.js'; -import PatchDeviceController from './controller/patchDeviceController.js'; -import GetScriptsController from './controller/automation/getScriptsController.js'; -import GetScriptController from './controller/automation/getScriptController.js'; -import CreateScriptController from './controller/automation/createScriptController.js'; -import DeleteScriptController from './controller/automation/deleteScriptController.js'; -import GetLogController from './controller/automation/getLogController.js'; -import RunScriptController from './controller/automation/runScriptController.js'; -import StopScriptController from './controller/automation/stopScriptController.js'; -import StatusScriptController from './controller/automation/statusScriptController.js'; -import VirtualDeviceProvider from './device/protocol/virtual/virtualDeviceProvider.js'; -import VirtualDeviceProviderFactory from './device/protocol/virtual/virtualDeviceProviderFactory.js'; -import GetSettingsController from './controller/settings/getSettingsController.js'; -import PutSettingsController from './controller/settings/putSettingsController.js'; -import JsonSchemaValidatorFactory from './schemaValidation/JsonSchemaValidatorFactory.js'; -import VersionController from './controller/versionController.js'; -import SerialPortObserver from './device/transport/serialPortObserver.js'; -import Zc95DeviceFactory from './device/protocol/zc95/zc95DeviceFactory.js'; -import VirtualDeviceFactory from './device/protocol/virtual/virtualDeviceFactory.js'; -import SerialPortFactory from './factory/serialPortFactory.js'; -import Estim2bDeviceFactory from './device/protocol/estim2b/estim2bDeviceFactory.js'; -import EventEmitterFactory from './factory/eventEmitterFactory.js'; -import BleObserver from './device/transport/bleObserver.js'; -import SlvCtrlPlusSerialDeviceProvider from './device/protocol/slvCtrlPlus/slvCtrlPlusSerialDeviceProvider.js'; -import Zc95SerialDeviceProvider from './device/protocol/zc95/zc95SerialDeviceProvider.js'; -import EStim2bSerialDeviceProvider from './device/protocol/estim2b/estim2bSerialDeviceProvider.js'; -import ButtplugIoWebsocketDeviceProvider from './device/protocol/buttplugIo/buttplugIoWebsocketDeviceProvider.js'; -import AiroticDeviceProvider from './device/protocol/airotic/airoticDeviceProvider.js'; -import AiroticDeviceFactory from './device/protocol/airotic/airoticDeviceFactory.js'; -import KnownDeviceRegistry from './device/knownDeviceRegistry.js'; +import type { Ajv } from 'ajv'; +import type ClassToPlainSerializer from './serialization/classToPlainSerializer.js'; +import type PlainToClassSerializer from './serialization/plainToClassSerializer.js'; +import type Logger from './logging/Logger.js'; +import type DeviceManager from './device/deviceManager.js'; +import type SerialDeviceTransportFactory from './device/transport/serialDeviceTransportFactory.js'; +import type DeviceProviderFactory from './device/provider/deviceProviderFactory.js'; +import type SlvCtrlPlusDeviceFactory from './device/protocol/slvCtrlPlus/slvCtrlPlusDeviceFactory.js'; +import type ButtplugIoDeviceFactory from './device/protocol/buttplugIo/buttplugIoDeviceFactory.js'; +import type DeviceNameGenerator from './device/deviceNameGenerator.js'; +import type DeviceUpdaterInterface from './device/updater/deviceUpdaterInterface.js'; +import type UuidFactory from './factory/uuidFactory.js'; +import type DateFactory from './factory/dateFactory.js'; +import type Settings from './settings/settings.js'; +import type SettingsManager from './settings/settingsManager.js'; +import type ScriptRuntime from './automation/scriptRuntime.js'; +import type ScriptVmFactory from './automation/scriptVmFactory.js'; +import type ConnectedDeviceRepository from './repository/connectedDeviceRepository.js'; +import type AutomationScriptRepository from './repository/automationScriptRepository.js'; +import type DeviceProviderManager from './device/provider/deviceProviderManager.js'; +import type DeviceUpdateHandler from './socket/deviceUpdateHandler.js'; +import type HealthController from './controller/healthController.js'; +import type HealthMetricsCollector from './health/healthMetricsCollector.js'; +import type GetDevicesController from './controller/getDevicesController.js'; +import type GetDeviceController from './controller/getDeviceController.js'; +import type PatchDeviceController from './controller/patchDeviceController.js'; +import type GetScriptsController from './controller/automation/getScriptsController.js'; +import type GetScriptController from './controller/automation/getScriptController.js'; +import type CreateScriptController from './controller/automation/createScriptController.js'; +import type DeleteScriptController from './controller/automation/deleteScriptController.js'; +import type GetLogController from './controller/automation/getLogController.js'; +import type RunScriptController from './controller/automation/runScriptController.js'; +import type StopScriptController from './controller/automation/stopScriptController.js'; +import type StatusScriptController from './controller/automation/statusScriptController.js'; +import type VirtualDeviceProvider from './device/protocol/virtual/virtualDeviceProvider.js'; +import type VirtualDeviceProviderFactory from './device/protocol/virtual/virtualDeviceProviderFactory.js'; +import type GetSettingsController from './controller/settings/getSettingsController.js'; +import type PutSettingsController from './controller/settings/putSettingsController.js'; +import type JsonSchemaValidatorFactory from './schemaValidation/JsonSchemaValidatorFactory.js'; +import type VersionController from './controller/versionController.js'; +import type SerialPortObserver from './device/transport/serialPortObserver.js'; +import type Zc95DeviceFactory from './device/protocol/zc95/zc95DeviceFactory.js'; +import type VirtualDeviceFactory from './device/protocol/virtual/virtualDeviceFactory.js'; +import type SerialPortFactory from './factory/serialPortFactory.js'; +import type Estim2bDeviceFactory from './device/protocol/estim2b/estim2bDeviceFactory.js'; +import type EventEmitterFactory from './factory/eventEmitterFactory.js'; +import type BleObserver from './device/transport/bleObserver.js'; +import type SlvCtrlPlusSerialDeviceProvider from './device/protocol/slvCtrlPlus/slvCtrlPlusSerialDeviceProvider.js'; +import type Zc95SerialDeviceProvider from './device/protocol/zc95/zc95SerialDeviceProvider.js'; +import type EStim2bSerialDeviceProvider from './device/protocol/estim2b/estim2bSerialDeviceProvider.js'; +import type ButtplugIoWebsocketDeviceProvider from './device/protocol/buttplugIo/buttplugIoWebsocketDeviceProvider.js'; +import type AiroticDeviceProvider from './device/protocol/airotic/airoticDeviceProvider.js'; +import type AiroticDeviceFactory from './device/protocol/airotic/airoticDeviceFactory.js'; +import type KnownDeviceRegistry from './device/knownDeviceRegistry.js'; type ServiceMap = { /* serializerServiceProvider */ diff --git a/src/serviceProvider/automationServiceProvider.ts b/src/serviceProvider/automationServiceProvider.ts index f7de3c48..747d929f 100644 --- a/src/serviceProvider/automationServiceProvider.ts +++ b/src/serviceProvider/automationServiceProvider.ts @@ -1,8 +1,8 @@ -import { Pimple, ServiceProvider } from '@timesplinter/pimple'; +import type { Pimple, ServiceProvider } from '@timesplinter/pimple'; import ScriptRuntime from '../automation/scriptRuntime.js'; import ScriptVmFactory from '../automation/scriptVmFactory.js'; import fs from 'fs'; -import ServiceMap from '../serviceMap.js'; +import type ServiceMap from '../serviceMap.js'; export default class AutomationServiceProvider implements ServiceProvider { diff --git a/src/serviceProvider/controllerServiceProvider.ts b/src/serviceProvider/controllerServiceProvider.ts index e4370708..3f782f02 100644 --- a/src/serviceProvider/controllerServiceProvider.ts +++ b/src/serviceProvider/controllerServiceProvider.ts @@ -1,5 +1,5 @@ import GetDevicesController from '../controller/getDevicesController.js'; -import { Pimple, ServiceProvider } from '@timesplinter/pimple'; +import type { Pimple, ServiceProvider } from '@timesplinter/pimple'; import GetDeviceController from '../controller/getDeviceController.js'; import PatchDeviceController from '../controller/patchDeviceController.js'; import HealthController from '../controller/healthController.js'; @@ -13,7 +13,7 @@ import GetLogController from '../controller/automation/getLogController.js'; import StatusScriptController from '../controller/automation/statusScriptController.js'; import GetSettingsController from '../controller/settings/getSettingsController.js'; import PutSettingsController from '../controller/settings/putSettingsController.js'; -import ServiceMap from '../serviceMap.js'; +import type ServiceMap from '../serviceMap.js'; import VersionController from '../controller/versionController.js'; export default class ControllerServiceProvider implements ServiceProvider diff --git a/src/serviceProvider/deviceServiceProvider.ts b/src/serviceProvider/deviceServiceProvider.ts index 394eea63..fa70075f 100644 --- a/src/serviceProvider/deviceServiceProvider.ts +++ b/src/serviceProvider/deviceServiceProvider.ts @@ -1,20 +1,21 @@ -import { Pimple, ServiceProvider } from '@timesplinter/pimple'; +import type { Pimple, ServiceProvider } from '@timesplinter/pimple'; import DeviceManager from '../device/deviceManager.js'; import SlvCtrlPlusDeviceFactory from '../device/protocol/slvCtrlPlus/slvCtrlPlusDeviceFactory.js'; -import { adjectives, Config } from 'unique-names-generator'; +import type { Config } from 'unique-names-generator'; +import { adjectives } from 'unique-names-generator'; import DeviceNameGenerator from '../device/deviceNameGenerator.js'; import { starWarsNouns } from '../util/dictionary.js'; import BufferedDeviceUpdater from '../device/updater/bufferedDeviceUpdater.js'; import GenericDeviceUpdater from '../device/genericDeviceUpdater.js'; import SerialDeviceTransportFactory from '../device/transport/serialDeviceTransportFactory.js'; import DeviceProviderManager from '../device/provider/deviceProviderManager.js'; -import { AnyDeviceProvider } from '../device/provider/deviceProvider.js'; +import type { AnyDeviceProvider } from '../device/provider/deviceProvider.js'; import SlvCtrlPlusSerialDeviceProvider from '../device/protocol/slvCtrlPlus/slvCtrlPlusSerialDeviceProvider.js'; import ButtplugIoWebsocketDeviceProvider from '../device/protocol/buttplugIo/buttplugIoWebsocketDeviceProvider.js'; import ButtplugIoWebsocketDeviceProviderFactory from '../device/protocol/buttplugIo/buttplugIoWebsocketDeviceProviderFactory.js'; import ButtplugIoDeviceFactory from '../device/protocol/buttplugIo/buttplugIoDeviceFactory.js'; -import ServiceMap from '../serviceMap.js'; +import type ServiceMap from '../serviceMap.js'; import VirtualDeviceProvider from '../device/protocol/virtual/virtualDeviceProvider.js'; import VirtualDeviceProviderFactory from '../device/protocol/virtual/virtualDeviceProviderFactory.js'; import GenericVirtualDeviceFactory from '../device/protocol/virtual/genericVirtualDeviceFactory.js'; @@ -39,7 +40,7 @@ import Estim2bDeviceFactory from '../device/protocol/estim2b/estim2bDeviceFactor import BleObserver from '../device/transport/bleObserver.js'; import AiroticDeviceProvider from '../device/protocol/airotic/airoticDeviceProvider.js'; import AiroticDeviceFactory from '../device/protocol/airotic/airoticDeviceFactory.js'; -import DeviceProviderFactory from '../device/provider/deviceProviderFactory.js'; +import type DeviceProviderFactory from '../device/provider/deviceProviderFactory.js'; import KnownDeviceRegistry from '../device/knownDeviceRegistry.js'; export default class DeviceServiceProvider implements ServiceProvider { diff --git a/src/serviceProvider/factoryServiceProvider.ts b/src/serviceProvider/factoryServiceProvider.ts index 838b2d14..6999e244 100644 --- a/src/serviceProvider/factoryServiceProvider.ts +++ b/src/serviceProvider/factoryServiceProvider.ts @@ -1,7 +1,7 @@ -import { Pimple, ServiceProvider } from '@timesplinter/pimple'; +import type { Pimple, ServiceProvider } from '@timesplinter/pimple'; import UuidFactory from '../factory/uuidFactory.js'; import DateFactory from '../factory/dateFactory.js'; -import ServiceMap from '../serviceMap.js'; +import type ServiceMap from '../serviceMap.js'; import SerialPortFactory from '../factory/serialPortFactory.js'; import EventEmitterFactory from '../factory/eventEmitterFactory.js'; diff --git a/src/serviceProvider/healthServiceProvider.ts b/src/serviceProvider/healthServiceProvider.ts index 7140892f..3fedc691 100644 --- a/src/serviceProvider/healthServiceProvider.ts +++ b/src/serviceProvider/healthServiceProvider.ts @@ -1,5 +1,5 @@ -import { Pimple, ServiceProvider } from '@timesplinter/pimple'; -import ServiceMap from '../serviceMap.js'; +import type { Pimple, ServiceProvider } from '@timesplinter/pimple'; +import type ServiceMap from '../serviceMap.js'; import HealthMetricsCollector from '../health/healthMetricsCollector.js'; export default class HealthServiceProvider implements ServiceProvider diff --git a/src/serviceProvider/loggerServiceProvider.ts b/src/serviceProvider/loggerServiceProvider.ts index eba383c2..3cd520fb 100644 --- a/src/serviceProvider/loggerServiceProvider.ts +++ b/src/serviceProvider/loggerServiceProvider.ts @@ -1,8 +1,8 @@ -import { Pimple, ServiceProvider } from '@timesplinter/pimple'; +import type { Pimple, ServiceProvider } from '@timesplinter/pimple'; import { default as Pino } from 'pino'; -import Logger from '../logging/Logger.js'; +import type Logger from '../logging/Logger.js'; import PinoLogger from '../logging/PinoLogger.js'; -import ServiceMap from '../serviceMap.js'; +import type ServiceMap from '../serviceMap.js'; export default class LoggerServiceProvider implements ServiceProvider { diff --git a/src/serviceProvider/repositoryServiceProvider.ts b/src/serviceProvider/repositoryServiceProvider.ts index 892e5477..9a0eed7f 100644 --- a/src/serviceProvider/repositoryServiceProvider.ts +++ b/src/serviceProvider/repositoryServiceProvider.ts @@ -1,8 +1,8 @@ -import { Pimple, ServiceProvider } from '@timesplinter/pimple'; +import type { Pimple, ServiceProvider } from '@timesplinter/pimple'; import ConnectedDeviceRepository from '../repository/connectedDeviceRepository.js'; import AutomationScriptRepository from '../repository/automationScriptRepository.js'; import fs from 'fs'; -import ServiceMap from '../serviceMap.js'; +import type ServiceMap from '../serviceMap.js'; export default class RepositoryServiceProvider implements ServiceProvider { diff --git a/src/serviceProvider/schemaValidationServiceProvider.ts b/src/serviceProvider/schemaValidationServiceProvider.ts index 33fb1610..ae6dd2f1 100644 --- a/src/serviceProvider/schemaValidationServiceProvider.ts +++ b/src/serviceProvider/schemaValidationServiceProvider.ts @@ -1,5 +1,5 @@ -import { Pimple, ServiceProvider } from '@timesplinter/pimple'; -import ServiceMap from '../serviceMap.js'; +import type { Pimple, ServiceProvider } from '@timesplinter/pimple'; +import type ServiceMap from '../serviceMap.js'; import type { Ajv } from 'ajv'; import { Ajv2020 } from 'ajv/dist/2020.js'; import ajvFormatsPlugin from 'ajv-formats'; diff --git a/src/serviceProvider/serializationServiceProvider.ts b/src/serviceProvider/serializationServiceProvider.ts index 0c253a05..9195a56e 100644 --- a/src/serviceProvider/serializationServiceProvider.ts +++ b/src/serviceProvider/serializationServiceProvider.ts @@ -1,7 +1,7 @@ -import { Pimple, ServiceProvider } from '@timesplinter/pimple'; +import type { Pimple, ServiceProvider } from '@timesplinter/pimple'; import ClassToPlainSerializer from '../serialization/classToPlainSerializer.js'; import PlainToClassSerializer from '../serialization/plainToClassSerializer.js'; -import ServiceMap from '../serviceMap.js'; +import type ServiceMap from '../serviceMap.js'; export default class SerializationServiceProvider implements ServiceProvider { diff --git a/src/serviceProvider/settingsServiceProvider.ts b/src/serviceProvider/settingsServiceProvider.ts index 320d2901..f0cb04f0 100644 --- a/src/serviceProvider/settingsServiceProvider.ts +++ b/src/serviceProvider/settingsServiceProvider.ts @@ -1,8 +1,8 @@ -import { Pimple, ServiceProvider } from '@timesplinter/pimple'; +import type { Pimple, ServiceProvider } from '@timesplinter/pimple'; import SettingsManager from '../settings/settingsManager.js'; import os from 'os'; import fs from 'fs'; -import ServiceMap from '../serviceMap.js'; +import type ServiceMap from '../serviceMap.js'; export default class SettingsServiceProvider implements ServiceProvider { diff --git a/src/serviceProvider/socketServiceProvider.ts b/src/serviceProvider/socketServiceProvider.ts index 6ea91d92..5c1b4473 100644 --- a/src/serviceProvider/socketServiceProvider.ts +++ b/src/serviceProvider/socketServiceProvider.ts @@ -1,6 +1,6 @@ -import { Pimple, ServiceProvider } from '@timesplinter/pimple'; +import type { Pimple, ServiceProvider } from '@timesplinter/pimple'; import DeviceUpdateHandler from '../socket/deviceUpdateHandler.js'; -import ServiceMap from '../serviceMap.js'; +import type ServiceMap from '../serviceMap.js'; export default class SocketServiceProvider implements ServiceProvider { diff --git a/src/settings/deviceSource.ts b/src/settings/deviceSource.ts index 25b45f3b..a0b637c4 100644 --- a/src/settings/deviceSource.ts +++ b/src/settings/deviceSource.ts @@ -1,5 +1,5 @@ import { Exclude, Expose } from 'class-transformer'; -import { JsonObject } from '../types.js'; +import type { JsonObject } from '../types.js'; @Exclude() export default class DeviceSource diff --git a/src/settings/knownDevice.ts b/src/settings/knownDevice.ts index 6aab3116..ab8344d6 100644 --- a/src/settings/knownDevice.ts +++ b/src/settings/knownDevice.ts @@ -1,6 +1,6 @@ import { Exclude, Expose } from 'class-transformer'; -import { JsonObject } from '../types.js'; -import { DeviceId } from '../device/deviceId.js'; +import type { JsonObject } from '../types.js'; +import type { DeviceId } from '../device/deviceId.js'; @Exclude() export default class KnownDevice diff --git a/src/settings/serializedTypes.ts b/src/settings/serializedTypes.ts index cee56a58..08a5bbf9 100644 --- a/src/settings/serializedTypes.ts +++ b/src/settings/serializedTypes.ts @@ -1,4 +1,4 @@ -import { JsonObject } from '../types.js'; +import type { JsonObject } from '../types.js'; type SerializedKnownDevice = { id: string; diff --git a/src/settings/settings.ts b/src/settings/settings.ts index 3316f3dd..801fac35 100644 --- a/src/settings/settings.ts +++ b/src/settings/settings.ts @@ -2,7 +2,7 @@ import { Exclude, Expose, Transform } from 'class-transformer'; import KnownDevice from './knownDevice.js'; import createMapTransformFn from '../util/createMapTransformFn.js'; import DeviceSource from './deviceSource.js'; -import { DeviceId } from '../device/deviceId.js'; +import type { DeviceId } from '../device/deviceId.js'; import { Type } from '@sinclair/typebox'; export const SettingsSchema = Type.Object({ diff --git a/src/settings/settingsManager.ts b/src/settings/settingsManager.ts index 4f8896af..792475a1 100644 --- a/src/settings/settingsManager.ts +++ b/src/settings/settingsManager.ts @@ -1,15 +1,17 @@ import fs from 'fs'; -import { watch, FSWatcher } from 'chokidar'; -import PlainToClassSerializer from '../serialization/plainToClassSerializer.js'; -import ClassToPlainSerializer from '../serialization/classToPlainSerializer.js'; +import type { FSWatcher } from 'chokidar'; +import { watch } from 'chokidar'; +import type PlainToClassSerializer from '../serialization/plainToClassSerializer.js'; +import type ClassToPlainSerializer from '../serialization/classToPlainSerializer.js'; import Settings, { SettingsSchema } from './settings.js'; import onChange from 'on-change'; import DeviceSource from './deviceSource.js'; import SlvCtrlPlusSerialDeviceProvider from '../device/protocol/slvCtrlPlus/slvCtrlPlusSerialDeviceProvider.js'; -import Logger from '../logging/Logger.js'; -import EventEmitter from 'events'; -import SettingsEventType from './settingsEventType.js'; +import type Logger from '../logging/Logger.js'; +import type EventEmitter from 'events'; +import type SettingsEventType from './settingsEventType.js'; import { logError } from '../util/error.js'; +import { JSON_INDENTION_SPACES } from '../util/numbers.js'; type SettingsEvents = { [SettingsEventType.changed]: (settings: Settings) => void; @@ -140,7 +142,7 @@ export default class SettingsManager try { const normalized = this.classToPlainSerializer.transform(this.settings); - const json = JSON.stringify(normalized, null, 4); + const json = JSON.stringify(normalized, null, JSON_INDENTION_SPACES); fs.writeFileSync(this.settingsFilePath, json); // Remember what we just wrote so the file watcher can recognize and ignore this write diff --git a/src/socket/deviceUpdateHandler.ts b/src/socket/deviceUpdateHandler.ts index 26b238bc..40f17470 100644 --- a/src/socket/deviceUpdateHandler.ts +++ b/src/socket/deviceUpdateHandler.ts @@ -1,7 +1,7 @@ -import ConnectedDeviceRepository from '../repository/connectedDeviceRepository.js'; -import DeviceUpdaterInterface from '../device/updater/deviceUpdaterInterface.js'; -import { DeviceUpdateData } from './types.js'; -import Logger from '../logging/Logger.js'; +import type ConnectedDeviceRepository from '../repository/connectedDeviceRepository.js'; +import type DeviceUpdaterInterface from '../device/updater/deviceUpdaterInterface.js'; +import type { DeviceUpdateData } from './types.js'; +import type Logger from '../logging/Logger.js'; import { logError } from '../util/error.js'; export default class DeviceUpdateHandler diff --git a/src/socket/types.ts b/src/socket/types.ts index 649f0391..fd9ce80b 100644 --- a/src/socket/types.ts +++ b/src/socket/types.ts @@ -1,12 +1,12 @@ -import { Server } from 'socket.io'; -import { DeviceData, AnyDeviceNotification } from '../device/device.js'; -import WebSocketEvent from '../device/webSocketEvent.js'; -import SettingsEventType from '../settings/settingsEventType.js'; -import AutomationEventType from '../automation/automationEventType.js'; -import { SerializedHealthMetrics } from '../health/serializedTypes.js'; -import { SerializedDevice } from '../device/serializedTypes.js'; -import { SerializedSettings } from '../settings/serializedTypes.js'; -import { DeviceId } from '../device/deviceId.js'; +import type { Server } from 'socket.io'; +import type { DeviceData, AnyDeviceNotification } from '../device/device.js'; +import type WebSocketEvent from '../device/webSocketEvent.js'; +import type SettingsEventType from '../settings/settingsEventType.js'; +import type AutomationEventType from '../automation/automationEventType.js'; +import type { SerializedHealthMetrics } from '../health/serializedTypes.js'; +import type { SerializedDevice } from '../device/serializedTypes.js'; +import type { SerializedSettings } from '../settings/serializedTypes.js'; +import type { DeviceId } from '../device/deviceId.js'; export type DeviceUpdateData = { deviceId: DeviceId, data: DeviceData }; diff --git a/src/util/async.ts b/src/util/async.ts index 9a529550..cd08f19b 100644 --- a/src/util/async.ts +++ b/src/util/async.ts @@ -1,4 +1,6 @@ -export const sleep = (ms: number): Promise => new Promise(r => setTimeout(r, ms)); +export const sleep = async (ms: number): Promise => new Promise(r => { + setTimeout(r, ms); +}); export class IntervalTimeoutError extends Error { public constructor(timeoutMs: number) { @@ -49,11 +51,11 @@ export const setIntervalAsync = ( if (undefined !== options.timeoutMs) { const timeoutMs = options.timeoutMs; - promises.push(new Promise((_, reject) => + promises.push(new Promise((_, reject) => { timeoutHandle = setTimeout(() => { reject(new IntervalTimeoutError(timeoutMs)); - }, timeoutMs)), - ); + }, timeoutMs); + })); } try { @@ -68,8 +70,9 @@ export const setIntervalAsync = ( clearTimeout(timeoutHandle); if (!stopped) { - // eslint-disable-next-line @typescript-eslint/no-misused-promises - timer = setTimeout(loop, options.intervalMs); + timer = setTimeout(() => { + void loop(); + }, options.intervalMs); } } }; @@ -77,8 +80,9 @@ export const setIntervalAsync = ( if (options.runImmediately ?? true) { void loop(); } else { - // eslint-disable-next-line @typescript-eslint/no-misused-promises - timer = setTimeout(loop, options.intervalMs); + timer = setTimeout(() => { + void loop(); + }, options.intervalMs); } return { @@ -89,7 +93,7 @@ export const setIntervalAsync = ( }; }; -export const promiseWithTimeout = ( +export const promiseWithTimeout = async ( promise: Promise, timeoutMs: number, timeoutMessage = `Promise timed out after ${timeoutMs}ms`, diff --git a/src/util/color.ts b/src/util/color.ts index b70e2f11..f729712e 100644 --- a/src/util/color.ts +++ b/src/util/color.ts @@ -1,36 +1,47 @@ +import { BYTE_MAX } from './numbers.js'; + /** * Converts a byte-scaled HSV color (h, s, v each 0-255) into an RGB color (each 0-255). */ +const HUE_CIRCLE_DEGREES = 360; +const HUE_SECTOR_DEGREES = 60; +const HUE_MODULO_DIVISOR = 2; +const HUE_SECTOR_1_END = 60; +const HUE_SECTOR_2_END = 120; +const HUE_SECTOR_3_END = 180; +const HUE_SECTOR_4_END = 240; +const HUE_SECTOR_5_END = 300; + export const hsvByteToRgb = (h: number, s: number, v: number): { r: number, g: number, b: number } => { - const hue = (h / 255) * 360; - const saturation = s / 255; - const value = v / 255; + const hue = (h / BYTE_MAX) * HUE_CIRCLE_DEGREES; + const saturation = s / BYTE_MAX; + const value = v / BYTE_MAX; const c = value * saturation; - const x = c * (1 - Math.abs(((hue / 60) % 2) - 1)); + const x = c * (1 - Math.abs(((hue / HUE_SECTOR_DEGREES) % HUE_MODULO_DIVISOR) - 1)); const m = value - c; let rPrime: number; let gPrime: number; let bPrime: number; - if (hue < 60) { + if (hue < HUE_SECTOR_1_END) { [rPrime, gPrime, bPrime] = [c, x, 0]; - } else if (hue < 120) { + } else if (hue < HUE_SECTOR_2_END) { [rPrime, gPrime, bPrime] = [x, c, 0]; - } else if (hue < 180) { + } else if (hue < HUE_SECTOR_3_END) { [rPrime, gPrime, bPrime] = [0, c, x]; - } else if (hue < 240) { + } else if (hue < HUE_SECTOR_4_END) { [rPrime, gPrime, bPrime] = [0, x, c]; - } else if (hue < 300) { + } else if (hue < HUE_SECTOR_5_END) { [rPrime, gPrime, bPrime] = [x, 0, c]; } else { [rPrime, gPrime, bPrime] = [c, 0, x]; } return { - r: Math.round((rPrime + m) * 255), - g: Math.round((gPrime + m) * 255), - b: Math.round((bPrime + m) * 255), + r: Math.round((rPrime + m) * BYTE_MAX), + g: Math.round((gPrime + m) * BYTE_MAX), + b: Math.round((bPrime + m) * BYTE_MAX), }; }; diff --git a/src/util/devNullStream.ts b/src/util/devNullStream.ts index 2a9fabc9..fa24db93 100644 --- a/src/util/devNullStream.ts +++ b/src/util/devNullStream.ts @@ -2,12 +2,12 @@ import { Writable } from 'stream'; export default class DevNullStream extends Writable { - private readonly timeoutMs: number; + private readonly drainIdleTimeoutMs: number; private timer?: NodeJS.Timeout; - public constructor(timeoutMs = 500) { + public constructor(drainIdleTimeoutMs = 500) { super(); - this.timeoutMs = timeoutMs; + this.drainIdleTimeoutMs = drainIdleTimeoutMs; this.resetTimer(); } @@ -23,6 +23,8 @@ export default class DevNullStream extends Writable private resetTimer(): void { if (this.timer) clearTimeout(this.timer); - this.timer = setTimeout(() => this.emit('idle'), this.timeoutMs); + this.timer = setTimeout(() => { + this.emit('idle'); + }, this.drainIdleTimeoutMs); } } diff --git a/src/util/error.ts b/src/util/error.ts index 3867aadb..d50eee56 100644 --- a/src/util/error.ts +++ b/src/util/error.ts @@ -1,5 +1,5 @@ import BaseError from 'modern-errors'; -import Logger from '../logging/Logger.js'; +import type Logger from '../logging/Logger.js'; import { IntervalTimeoutError } from './async.js'; export const logError = (logger: Logger, message: string, error: unknown): void => { diff --git a/src/util/expressUtils.ts b/src/util/expressUtils.ts index 0a6b5ae1..a0c44f93 100644 --- a/src/util/expressUtils.ts +++ b/src/util/expressUtils.ts @@ -1,7 +1,7 @@ import type { Request, Response } from 'express'; import type { Container } from '@timesplinter/pimple'; import type ServiceMap from '../serviceMap.js'; -import ControllerInterface from '../controller/controllerInterface.js'; +import type ControllerInterface from '../controller/controllerInterface.js'; export type ControllerKey = { [K in keyof ServiceMap]: K extends `controller.${string}` ? K : never @@ -11,7 +11,7 @@ export const executeController = ( container: Container, controllerName: ControllerKey, ): (req: Request, res: Response) => void | Promise => { - return (req: Request, res: Response) => { + return async (req: Request, res: Response) => { const controller: ControllerInterface = container.get(controllerName); return controller.execute(req, res); }; diff --git a/src/util/json.ts b/src/util/json.ts index f861bce8..7504af9d 100644 --- a/src/util/json.ts +++ b/src/util/json.ts @@ -1,5 +1,5 @@ -import { Static } from '@sinclair/typebox'; -import JsonSchemaValidator from '../schemaValidation/JsonSchemaValidator.js'; +import type { Static } from '@sinclair/typebox'; +import type JsonSchemaValidator from '../schemaValidation/JsonSchemaValidator.js'; import type { TSchema } from '@sinclair/typebox'; import SchemaValidationError from '../schemaValidation/schemaValidationError.js'; import { normalizeError } from './typeUtils.js'; diff --git a/src/util/latestOnlyTaskQueue.ts b/src/util/latestOnlyTaskQueue.ts index 56c8e700..3a79dabe 100644 --- a/src/util/latestOnlyTaskQueue.ts +++ b/src/util/latestOnlyTaskQueue.ts @@ -1,16 +1,17 @@ -import { SequentialTaskQueue, CancellationToken, CancellablePromiseLike } from '@timesplinter/sequential-task-queue'; +import type { CancellationToken } from '@timesplinter/sequential-task-queue'; +import { SequentialTaskQueue } from '@timesplinter/sequential-task-queue'; export default class LatestOnlyTaskQueue { - private queue = new SequentialTaskQueue(); + private readonly queue = new SequentialTaskQueue(); - public run( + public async run( task: (token: CancellationToken) => Promise, - ): CancellablePromiseLike { + ): Promise { void this.queue.cancel(); return this.queue.push(task); } - public cancel(reason?: unknown): Promise { + public async cancel(reason?: unknown): Promise { return this.queue.cancel(reason); } } diff --git a/src/util/numbers.ts b/src/util/numbers.ts index 835111b0..08d8b638 100644 --- a/src/util/numbers.ts +++ b/src/util/numbers.ts @@ -1,4 +1,11 @@ -/* eslint-disable @typescript-eslint/consistent-type-assertions */ +/* eslint-disable @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-unsafe-type-assertion, @typescript-eslint/no-redeclare */ +export const MIN_AS_SECONDS = 60; +export const SECOND_AS_MILLISECONDS = 1000; +export const BITS_PER_BYTE = 8; +export const BYTE_MAX = 255; +export const JSON_INDENTION_SPACES = 2; +export const HALF_FACTOR = 0.5; + export type Int = number & { __integer__: never }; export const Int = { diff --git a/src/util/objects.ts b/src/util/objects.ts index 37b1f4c8..252a02da 100644 --- a/src/util/objects.ts +++ b/src/util/objects.ts @@ -1,7 +1,7 @@ type StringKey = Extract; export const getTypedKeys = (obj: T): (StringKey)[] => { - // eslint-disable-next-line @typescript-eslint/consistent-type-assertions + // eslint-disable-next-line @typescript-eslint/consistent-type-assertions, @typescript-eslint/no-unsafe-type-assertion return Object.keys(obj) as StringKey[]; }; diff --git a/src/util/process.ts b/src/util/process.ts index d2ead146..7b64a678 100644 --- a/src/util/process.ts +++ b/src/util/process.ts @@ -1,5 +1,4 @@ -import { - spawn, +import type { ChildProcess, ChildProcessByStdio, ChildProcessWithoutNullStreams, @@ -7,23 +6,25 @@ import { SpawnOptionsWithoutStdio, SpawnOptionsWithStdioTuple, StdioNull, - StdioPipe, + StdioPipe } from 'node:child_process'; +import { + spawn, } from 'node:child_process'; -import { Readable, Writable } from 'stream'; +import type { Readable, Writable } from 'stream'; // Overloads matching node:child_process spawn signatures -export function spawnProcess(command: string, args?: readonly string[], options?: SpawnOptionsWithoutStdio): Promise; -export function spawnProcess(command: string, args: readonly string[], options: SpawnOptionsWithStdioTuple): Promise>; -export function spawnProcess(command: string, args: readonly string[], options: SpawnOptionsWithStdioTuple): Promise>; -export function spawnProcess(command: string, args: readonly string[], options: SpawnOptionsWithStdioTuple): Promise>; -export function spawnProcess(command: string, args: readonly string[], options: SpawnOptionsWithStdioTuple): Promise>; -export function spawnProcess(command: string, args: readonly string[], options: SpawnOptionsWithStdioTuple): Promise>; -export function spawnProcess(command: string, args: readonly string[], options: SpawnOptionsWithStdioTuple): Promise>; -export function spawnProcess(command: string, args: readonly string[], options: SpawnOptionsWithStdioTuple): Promise>; -export function spawnProcess(command: string, args: readonly string[], options: SpawnOptionsWithStdioTuple): Promise>; -export function spawnProcess(command: string, args: readonly string[], options: SpawnOptions): Promise; -export function spawnProcess( +export async function spawnProcess(command: string, args?: readonly string[], options?: SpawnOptionsWithoutStdio): Promise; +export async function spawnProcess(command: string, args: readonly string[], options: SpawnOptionsWithStdioTuple): Promise>; +export async function spawnProcess(command: string, args: readonly string[], options: SpawnOptionsWithStdioTuple): Promise>; +export async function spawnProcess(command: string, args: readonly string[], options: SpawnOptionsWithStdioTuple): Promise>; +export async function spawnProcess(command: string, args: readonly string[], options: SpawnOptionsWithStdioTuple): Promise>; +export async function spawnProcess(command: string, args: readonly string[], options: SpawnOptionsWithStdioTuple): Promise>; +export async function spawnProcess(command: string, args: readonly string[], options: SpawnOptionsWithStdioTuple): Promise>; +export async function spawnProcess(command: string, args: readonly string[], options: SpawnOptionsWithStdioTuple): Promise>; +export async function spawnProcess(command: string, args: readonly string[], options: SpawnOptionsWithStdioTuple): Promise>; +export async function spawnProcess(command: string, args: readonly string[], options: SpawnOptions): Promise; +export async function spawnProcess( command: string, args?: readonly string[], options?: SpawnOptions, diff --git a/tests/unit/automation/scriptRuntime.spec.ts b/tests/unit/automation/scriptRuntime.spec.ts index 51e740b3..706b49be 100644 --- a/tests/unit/automation/scriptRuntime.spec.ts +++ b/tests/unit/automation/scriptRuntime.spec.ts @@ -26,7 +26,7 @@ class StubDevice extends Device { logger.child.mockReturnValue(mock()); super( - id, name, 'test', new Date(), true, + {deviceId: id, deviceName: name, provider: 'test', connectedSince: new Date(), controllable: true}, { label: StrDeviceAttribute.createInitialized( 'label', undefined, DeviceAttributeModifier.readWrite, 'hello' diff --git a/tests/unit/device/bleDevice.spec.ts b/tests/unit/device/bleDevice.spec.ts index b8403552..465dce1e 100644 --- a/tests/unit/device/bleDevice.spec.ts +++ b/tests/unit/device/bleDevice.spec.ts @@ -24,7 +24,7 @@ class TestBleDevice extends BleDevice { eventEmitter: EventEmitter, logger: Logger, ) { - super(deviceId, deviceName, provider, peripheral, connectedSince, controllable, attributes, config, eventEmitter, logger); + super({ deviceId, deviceName, provider, connectedSince, controllable }, peripheral, attributes, config, eventEmitter, logger); } public async setAttribute, V extends AttributeValueOf>( diff --git a/tests/unit/device/protocol/airotic/airoticDevice.spec.ts b/tests/unit/device/protocol/airotic/airoticDevice.spec.ts index 0d48d793..b6db5a0a 100644 --- a/tests/unit/device/protocol/airotic/airoticDevice.spec.ts +++ b/tests/unit/device/protocol/airotic/airoticDevice.spec.ts @@ -38,14 +38,16 @@ describe('AiroticDevice', () => { function createDevice(): AiroticDevice { return new AiroticDevice( - DeviceId.create('airotic-device'), - 'Airotic', - 'airotic-provider', + { + deviceId: DeviceId.create('airotic-device'), + deviceName: 'Airotic', + provider: 'airotic-provider', + connectedSince: new Date(), + controllable: true, + }, mockPeripheral, mockTransport, mockHandler, - new Date(), - true, createAttributes(), {}, new EventEmitter(), @@ -505,7 +507,7 @@ describe('AiroticDevice', () => { await device.setAttribute('reboot', true); - expect(sleep).toHaveBeenCalledWith(500); + expect(sleep).toHaveBeenCalledWith(100); }); }); }); diff --git a/tests/unit/device/protocol/buttplugIo/buttplugIoDevice.spec.ts b/tests/unit/device/protocol/buttplugIo/buttplugIoDevice.spec.ts index 5f66ec59..abc6fd6e 100644 --- a/tests/unit/device/protocol/buttplugIo/buttplugIoDevice.spec.ts +++ b/tests/unit/device/protocol/buttplugIo/buttplugIoDevice.spec.ts @@ -19,11 +19,14 @@ describe('ButtplugIoDevice', () => { function createDevice(buttplugDeviceMock: ButtplugClientDevice, attrs: ButtplugIoDeviceAttributes): ButtplugIoDevice { return new ButtplugIoDevice( - DeviceId.create('device-id'), - 'device name', + { + deviceId: DeviceId.create('device-id'), + deviceName: 'device name', + provider: 'deviceProvider', + connectedSince: new Date(), + controllable: true, + }, 'device model', - 'buttplugIo', - new Date(), buttplugDeviceMock, attrs, new EventEmitter(), diff --git a/tests/unit/device/protocol/estim2b/estim2bDevice.spec.ts b/tests/unit/device/protocol/estim2b/estim2bDevice.spec.ts index 33cfbe9e..0238d47d 100644 --- a/tests/unit/device/protocol/estim2b/estim2bDevice.spec.ts +++ b/tests/unit/device/protocol/estim2b/estim2bDevice.spec.ts @@ -64,11 +64,13 @@ describe('EStim2bDevice', () => { transport: DeviceBidirectionalTransport, ): EStim2bDevice { return new EStim2bDevice( - DeviceId.create('device-id'), - 'ET-312', - 'estim2b', - new Date(), - true, + { + deviceId: DeviceId.create('device-id'), + deviceName: 'ET-312', + provider: 'estim2b', + connectedSince: new Date(), + controllable: true, + }, status, protocol, transport, diff --git a/tests/unit/device/protocol/slvCtrlPlus/genericSlvCtrlPlusDevice.spec.ts b/tests/unit/device/protocol/slvCtrlPlus/genericSlvCtrlPlusDevice.spec.ts index 655e5d5f..0cac0998 100644 --- a/tests/unit/device/protocol/slvCtrlPlus/genericSlvCtrlPlusDevice.spec.ts +++ b/tests/unit/device/protocol/slvCtrlPlus/genericSlvCtrlPlusDevice.spec.ts @@ -27,8 +27,21 @@ describe('GenericSlvCtrlPlusDevice', () => { const provider = 'dummy'; return new GenericSlvCtrlPlusDevice( - fwVersion, deviceUuid, deviceName, model, provider, new Date(), protocol, transport, protocolVersion, attrs, - mock(), mock(), + { + deviceId: deviceUuid, + deviceName, + provider, + connectedSince: new Date(), + controllable: true, + }, + fwVersion, + model, + protocol, + transport, + protocolVersion, + attrs, + mock(), + mock(), ); } @@ -193,12 +206,15 @@ describe('GenericSlvCtrlPlusDevice', () => { const mockLogger = mock(); const device = new GenericSlvCtrlPlusDevice( + { + deviceId: DeviceId.create('device-id'), + deviceName: 'Device', + provider: 'provider', + connectedSince: new Date(), + controllable: true, + }, 10000, - DeviceId.create('device-id'), - 'Device', 'model', - 'provider', - new Date(), mockProtocol, mockTransport, 10000, @@ -245,21 +261,12 @@ describe('GenericSlvCtrlPlusDevice', () => { const mockTransport = mock(); const mockLogger = mock(); - const device = new GenericSlvCtrlPlusDevice( - 10000, - DeviceId.create('device-id'), - 'Device', - 'model', - 'provider', - new Date(), - mockProtocol, - mockTransport, - 10000, + const device = createDevice( { bool: new BoolDeviceAttribute('bool', 'Bool', DeviceAttributeModifier.readWrite, true), }, - new EventEmitter(), - mockLogger, + mockProtocol, + mockTransport, ); const statusCommand = { command: 'status', args: [] }; @@ -296,21 +303,12 @@ describe('GenericSlvCtrlPlusDevice', () => { const mockTransport = mock(); const mockLogger = mock(); - const device = new GenericSlvCtrlPlusDevice( - 10000, - DeviceId.create('device-id'), - 'Device', - 'model', - 'provider', - new Date(), - mockProtocol, - mockTransport, - 10000, + const device = createDevice( { bool: new BoolDeviceAttribute('bool', 'Bool', DeviceAttributeModifier.readWrite, undefined), }, - new EventEmitter(), - mockLogger, + mockProtocol, + mockTransport, ); const statusCommand = { command: 'status', args: [] }; diff --git a/tests/unit/device/protocol/virtual/display/displayVirtualDevice.spec.ts b/tests/unit/device/protocol/virtual/display/displayVirtualDevice.spec.ts index 36097695..4f75f891 100644 --- a/tests/unit/device/protocol/virtual/display/displayVirtualDevice.spec.ts +++ b/tests/unit/device/protocol/virtual/display/displayVirtualDevice.spec.ts @@ -12,12 +12,15 @@ describe('DisplayVirtualDevice', () => { function createDevice(): VirtualDevice { const virtualDeviceLogic = new DisplayVirtualDeviceLogic({}); return new VirtualDevice( + { + deviceId: DeviceId.create('device-id'), + deviceName: 'device name', + provider: 'buttplugIo', + connectedSince: new Date(), + controllable: true, + }, '1.0.0', - DeviceId.create('device-id'), - 'device name', 'device model', - 'buttplugIo', - new Date(), {}, virtualDeviceLogic, new EventEmitter(), diff --git a/tests/unit/device/protocol/zc95/zc95Device.spec.ts b/tests/unit/device/protocol/zc95/zc95Device.spec.ts index 241060cb..13cdbc7a 100644 --- a/tests/unit/device/protocol/zc95/zc95Device.spec.ts +++ b/tests/unit/device/protocol/zc95/zc95Device.spec.ts @@ -82,14 +82,16 @@ describe('Zc95Device', () => { function createDevice(attrs: Zc95DeviceAttributes): Zc95Device { return new Zc95Device( - DeviceId.create('device-id'), - 'Test Device', - 'zc95', - new Date(), + { + deviceId: DeviceId.create('device-id'), + deviceName: 'Test Device', + provider: 'zc95', + connectedSince: new Date(), + controllable: true, + }, '1.0.0', mockProtocol, mockTransport, - true, attrs, {}, mockMsgFactory, diff --git a/tests/unit/device/provider/deviceProvider.spec.ts b/tests/unit/device/provider/deviceProvider.spec.ts index df9c9eaf..8916c237 100644 --- a/tests/unit/device/provider/deviceProvider.spec.ts +++ b/tests/unit/device/provider/deviceProvider.spec.ts @@ -214,7 +214,7 @@ describe('DeviceProvider', () => { const deviceManager = new DeviceManager(new EventEmitter(), settingsManager, logger); let resolveCreateDevice!: (device: AnyDevice) => void; - const createDevicePromise = new Promise((resolve) => { resolveCreateDevice = resolve; }); + const createDevicePromise = new Promise((resolve) => { resolveCreateDevice = resolve }); const provider = new SlowCreateDeviceProvider(deviceManager, createDevicePromise); await provider.start(); diff --git a/tests/unit/device/testDevice.ts b/tests/unit/device/testDevice.ts index 70077c99..f97b0cdd 100644 --- a/tests/unit/device/testDevice.ts +++ b/tests/unit/device/testDevice.ts @@ -16,7 +16,7 @@ export default class TestDevice extends Device const logger = mock(); logger.child.mockReturnValue(mock()); - super(deviceId, deviceName, 'dummy', connectedSince, controllable, {}, {}, eventEmitter, logger); + super({ deviceId, deviceName, provider: 'dummy', connectedSince, controllable }, {}, {}, eventEmitter, logger); } public async setAttribute< diff --git a/tsconfig.tooling.json b/tsconfig.tooling.json new file mode 100644 index 00000000..b05051d1 --- /dev/null +++ b/tsconfig.tooling.json @@ -0,0 +1,14 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "rootDir": ".", + "noEmit": true, + "noUncheckedIndexedAccess": false + }, + "include": [ + "eslint.config.ts", + "vitest.config.ts", + "vitest.config.*.ts", + "types/**/*" + ] +} diff --git a/types/eslint-plugin-sort-class-members.d.ts b/types/eslint-plugin-sort-class-members.d.ts new file mode 100644 index 00000000..7665250c --- /dev/null +++ b/types/eslint-plugin-sort-class-members.d.ts @@ -0,0 +1 @@ +declare module 'eslint-plugin-sort-class-members'; From 16c821924de5b8ffd88c23e8dae3dbdcd7d249b1 Mon Sep 17 00:00:00 2001 From: HRS Date: Sat, 8 Aug 2026 22:42:50 +0200 Subject: [PATCH 14/15] Clean --- eslint.config.ts | 244 +++++++++--------- .../automation/getScriptsController.ts | 2 +- src/device/protocol/deviceProtocol.ts | 6 + .../slvCtrlPlus/genericSlvCtrlPlusDevice.ts | 4 +- .../slvCtrlPlus/slvCtrlProtocolLegacy.ts | 2 - .../protocol/virtual/virtualDeviceFactory.ts | 2 +- .../virtual/virtualDeviceLogicFactory.ts | 4 +- src/device/protocol/zc95/zc95Device.ts | 2 +- .../protocol/zc95/zc95MessageFactory.ts | 7 +- src/device/provider/deviceProviderFactory.ts | 5 + .../transport/deviceWritableTransport.ts | 10 +- 11 files changed, 151 insertions(+), 137 deletions(-) diff --git a/eslint.config.ts b/eslint.config.ts index 4fb38ca7..8744eb7d 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -5,7 +5,7 @@ import jsdoc from 'eslint-plugin-jsdoc'; import preferArrowFunctions from 'eslint-plugin-prefer-arrow-functions'; import globals from 'globals'; import stylistic from '@stylistic/eslint-plugin'; -import sortClassMembers from "eslint-plugin-sort-class-members"; +import sortClassMembers from 'eslint-plugin-sort-class-members'; export default defineConfig([ globalIgnores(['**/*.js', '**/*.cjs', '**/*.mjs']), @@ -27,141 +27,141 @@ export default defineConfig([ } }, plugins: { - "@jsdoc": jsdoc, - "@sortClassMembers": sortClassMembers, + '@jsdoc': jsdoc, + '@sortClassMembers': sortClassMembers, }, rules: { // Extremely slow (deep readonly type-checking); can hang lint on this codebase. - "@typescript-eslint/prefer-readonly-parameter-types": "off", - "@typescript-eslint/no-magic-numbers": ["error", { - "ignoreReadonlyClassProperties": true, - "ignoreTypeIndexes": true, - "ignoreEnums": true, - "ignoreNumericLiteralTypes": true, - "ignoreArrayIndexes": true, - "ignoreDefaultValues": true, - "ignoreClassFieldInitialValues": true, - "ignore": [-1, 0, 1, 10], + '@typescript-eslint/prefer-readonly-parameter-types': 'off', + '@typescript-eslint/no-magic-numbers': ['error', { + 'ignoreReadonlyClassProperties': true, + 'ignoreTypeIndexes': true, + 'ignoreEnums': true, + 'ignoreNumericLiteralTypes': true, + 'ignoreArrayIndexes': true, + 'ignoreDefaultValues': true, + 'ignoreClassFieldInitialValues': true, + 'ignore': [-1, 0, 1, 10], }], - "@typescript-eslint/no-confusing-void-expression": [ - "error", + '@typescript-eslint/no-confusing-void-expression': [ + 'error', { - "ignoreArrowShorthand": true, + 'ignoreArrowShorthand': true, } ], // your overrides - "@typescript-eslint/array-type": [ - "error", + '@typescript-eslint/array-type': [ + 'error', { - "default": "array" + 'default': 'array' } ], - "@typescript-eslint/no-restricted-types": [ - "error", + '@typescript-eslint/no-restricted-types': [ + 'error', { - "types": { - "Object": "Avoid using the `Object` type. Did you mean `object`?", - "Function": "Avoid using the `Function` type. Prefer a specific function type, like `() => void`.", - "Boolean": "Avoid using the `Boolean` type. Did you mean `boolean`?", - "Number": "Avoid using the `Number` type. Did you mean `number`?", - "String": "Avoid using the `String` type. Did you mean `string`?", - "Symbol": "Avoid using the `Symbol` type. Did you mean `symbol`?" + 'types': { + 'Object': 'Avoid using the `Object` type. Did you mean `object`?', + 'Function': 'Avoid using the `Function` type. Prefer a specific function type, like `() => void`.', + 'Boolean': 'Avoid using the `Boolean` type. Did you mean `boolean`?', + 'Number': 'Avoid using the `Number` type. Did you mean `number`?', + 'String': 'Avoid using the `String` type. Did you mean `string`?', + 'Symbol': 'Avoid using the `Symbol` type. Did you mean `symbol`?' } } ], - "@typescript-eslint/consistent-type-assertions": [ - "error", + '@typescript-eslint/consistent-type-assertions': [ + 'error', { - "assertionStyle": "never", + 'assertionStyle': 'never', } ], - "@typescript-eslint/explicit-function-return-type": "error", - "@typescript-eslint/explicit-member-accessibility": "error", - "@typescript-eslint/explicit-module-boundary-types": "error", - "@typescript-eslint/naming-convention": [ - "error", + '@typescript-eslint/explicit-function-return-type': 'error', + '@typescript-eslint/explicit-member-accessibility': 'error', + '@typescript-eslint/explicit-module-boundary-types': 'error', + '@typescript-eslint/naming-convention': [ + 'error', { - "selector": "classProperty", - "modifiers": ["static", "readonly"], - "format": ["camelCase", "UPPER_CASE"] + 'selector': 'classProperty', + 'modifiers': ['static', 'readonly'], + 'format': ['camelCase', 'UPPER_CASE'] }, { - "selector": "classProperty", - "format": ["camelCase"], - "leadingUnderscore": "allow" + 'selector': 'classProperty', + 'format': ['camelCase'], + 'leadingUnderscore': 'allow' } ], - "@typescript-eslint/no-floating-promises": [ - "warn", + '@typescript-eslint/no-floating-promises': [ + 'warn', { - "checkThenables": true + 'checkThenables': true } ], - "@typescript-eslint/consistent-type-definitions": [ - "error", - "type" + '@typescript-eslint/consistent-type-definitions': [ + 'error', + 'type' ], - "@typescript-eslint/parameter-properties": "error", - "@typescript-eslint/no-shadow": "error", - "@typescript-eslint/no-use-before-define": "error", - "@typescript-eslint/restrict-template-expressions": [ - "warn", + '@typescript-eslint/parameter-properties': 'error', + '@typescript-eslint/no-shadow': 'error', + '@typescript-eslint/no-use-before-define': 'error', + '@typescript-eslint/restrict-template-expressions': [ + 'warn', { - "allowNumber": true + 'allowNumber': true } ], - "@typescript-eslint/strict-boolean-expressions": "error", - "@typescript-eslint/class-methods-use-this": [ - "error", + '@typescript-eslint/strict-boolean-expressions': 'error', + '@typescript-eslint/class-methods-use-this': [ + 'error', { - "ignoreOverrideMethods": true, - "ignoreClassesThatImplementAnInterface": true, + 'ignoreOverrideMethods': true, + 'ignoreClassesThatImplementAnInterface': true, } ], - "@typescript-eslint/max-params": [ - "error", + '@typescript-eslint/max-params': [ + 'error', { - "max": 10 + 'max': 10 } ], - "@typescript-eslint/prefer-destructuring": "off", - "@typescript-eslint/member-ordering": "off", - "@stylistic/indent": ["error", 4], - "@stylistic/semi": ["error", "always", { "omitLastInOneLineBlock": true }], - "@stylistic/brace-style": "off", - "@stylistic/member-delimiter-style": [ - "error", + '@typescript-eslint/prefer-destructuring': 'off', + '@typescript-eslint/member-ordering': 'off', + '@stylistic/indent': ['error', 4], + '@stylistic/semi': ['error', 'always', { 'omitLastInOneLineBlock': true }], + '@stylistic/brace-style': 'off', + '@stylistic/member-delimiter-style': [ + 'error', { multiline: { - delimiter: "semi", + delimiter: 'semi', requireLast: true, }, singleline: { - delimiter: "comma", + delimiter: 'comma', requireLast: false, }, }, ], - "@stylistic/operator-linebreak": [ - "error", - "before", + '@stylistic/operator-linebreak': [ + 'error', + 'before', { overrides: { - "=": "after", + '=': 'after', }, }, ], - "@stylistic/indent-binary-ops": ["error", 4], - "@stylistic/arrow-parens": ["error", "as-needed"], - "@stylistic/padding-line-between-statements": ["error", { - blankLine: "always", - prev: ["type", "interface", "class", "enum"], - next: "export", + '@stylistic/indent-binary-ops': ['error', 4], + '@stylistic/arrow-parens': ['error', 'as-needed'], + '@stylistic/padding-line-between-statements': ['error', { + blankLine: 'always', + prev: ['type', 'interface', 'class', 'enum'], + next: 'export', }], - "@jsdoc/check-alignment": "error", - "@jsdoc/check-indentation": "error", - "@jsdoc/tag-lines": "error", - '@sortClassMembers/sort-class-members': ['error', { + '@jsdoc/check-alignment': 'error', + '@jsdoc/check-indentation': 'error', + '@jsdoc/tag-lines': 'error', + '@sortClassMembers/sort-class-members': ['warn', { order: [ '[public-static-readonly-properties]', '[protected-static-readonly-properties]', @@ -246,50 +246,50 @@ export default defineConfig([ ], }, }], - "complexity": "error", - "eqeqeq": [ - "error", - "smart" + 'complexity': 'error', + 'eqeqeq': [ + 'error', + 'smart' ], - "guard-for-in": "error", - "id-denylist": [ - "error", - "any", - "Number", - "number", - "String", - "string", - "Boolean", - "boolean", - "Undefined", - "undefined" + 'guard-for-in': 'error', + 'id-denylist': [ + 'error', + 'any', + 'Number', + 'number', + 'String', + 'string', + 'Boolean', + 'boolean', + 'Undefined', + 'undefined' ], - "id-match": "error", - "max-classes-per-file": "error", - "no-bitwise": "error", - "no-caller": "error", - "no-console": "warn", - "no-eval": "error", - "no-fallthrough": "off", - "no-magic-numbers": "off", - "no-new-wrappers": "error", - "no-undef-init": "error", - "no-underscore-dangle": [ - "error", + 'id-match': 'error', + 'max-classes-per-file': 'error', + 'no-bitwise': 'error', + 'no-caller': 'error', + 'no-console': 'warn', + 'no-eval': 'error', + 'no-fallthrough': 'off', + 'no-magic-numbers': 'off', + 'no-new-wrappers': 'error', + 'no-undef-init': 'error', + 'no-underscore-dangle': [ + 'error', { - "allowAfterThis": true + 'allowAfterThis': true } ], - "no-implicit-coercion": ["error", { - "boolean": true, - "number": true, - "string": true + 'no-implicit-coercion': ['error', { + 'boolean': true, + 'number': true, + 'string': true }], - "one-var": [ - "error", - "never" + 'one-var': [ + 'error', + 'never' ], - "radix": "error", + 'radix': 'error', }, }, { diff --git a/src/controller/automation/getScriptsController.ts b/src/controller/automation/getScriptsController.ts index c4f988df..814edbbe 100644 --- a/src/controller/automation/getScriptsController.ts +++ b/src/controller/automation/getScriptsController.ts @@ -9,7 +9,7 @@ export default class GetScriptsController implements ControllerInterface { private readonly automationScriptRepository: AutomationScriptRepositoryInterface; - private serializer: ClassToPlainSerializer; + private readonly serializer: ClassToPlainSerializer; public constructor( automationScriptRepository: AutomationScriptRepositoryInterface, diff --git a/src/device/protocol/deviceProtocol.ts b/src/device/protocol/deviceProtocol.ts index 2d7d39af..a6098d61 100644 --- a/src/device/protocol/deviceProtocol.ts +++ b/src/device/protocol/deviceProtocol.ts @@ -29,9 +29,15 @@ export type InferResponse = MR extends MessageWithResponse ? R : MR extends Message ? undefined : never; +// Method syntax throughout: protocol implementers narrow +// InferMessage/InferResponse to their concrete message types; property +// syntax would check contravariantly and reject that narrowing. type DeviceProtocol = { + // eslint-disable-next-line @typescript-eslint/method-signature-style encode(message: InferMessage): Buffer; + // eslint-disable-next-line @typescript-eslint/method-signature-style decode(data: Buffer): DecodeResult>; + // eslint-disable-next-line @typescript-eslint/method-signature-style isResponseMatchingMessage(response: InferResponse, message: MR): boolean; }; diff --git a/src/device/protocol/slvCtrlPlus/genericSlvCtrlPlusDevice.ts b/src/device/protocol/slvCtrlPlus/genericSlvCtrlPlusDevice.ts index d488b390..848d71f0 100644 --- a/src/device/protocol/slvCtrlPlus/genericSlvCtrlPlusDevice.ts +++ b/src/device/protocol/slvCtrlPlus/genericSlvCtrlPlusDevice.ts @@ -13,6 +13,8 @@ type AttributeValue = AttributeValu @Exclude() export default class GenericSlvCtrlPlusDevice extends SlvCtrlPlusDevice { + private static readonly DEFAULT_REFRESH_INTERVAL_MS = 100; + private readonly serialTimeout = 500; @Expose() @@ -84,7 +86,7 @@ export default class GenericSlvCtrlPlusDevice extends SlvCtrlPlusDevice } public override get getRefreshInterval(): number { - return 100; + return GenericSlvCtrlPlusDevice.DEFAULT_REFRESH_INTERVAL_MS; } protected getSerialTimeout(): number { diff --git a/src/device/protocol/slvCtrlPlus/slvCtrlProtocolLegacy.ts b/src/device/protocol/slvCtrlPlus/slvCtrlProtocolLegacy.ts index f306bea9..0dc291c2 100644 --- a/src/device/protocol/slvCtrlPlus/slvCtrlProtocolLegacy.ts +++ b/src/device/protocol/slvCtrlPlus/slvCtrlProtocolLegacy.ts @@ -16,8 +16,6 @@ import SlvCtrlProtocol from './slvCtrlProtocol.js'; import type { DecodeResult, InferMessage, InferResponse } from '../deviceProtocol.js'; import { hasExactLength } from '../../../util/typeUtils.js'; -export type StatusResponse = Record; - export default class SlvCtrlProtocolLegacy extends SlvCtrlProtocol { public override encode(command: InferMessage): Buffer { diff --git a/src/device/protocol/virtual/virtualDeviceFactory.ts b/src/device/protocol/virtual/virtualDeviceFactory.ts index 81cbe2c4..85664616 100644 --- a/src/device/protocol/virtual/virtualDeviceFactory.ts +++ b/src/device/protocol/virtual/virtualDeviceFactory.ts @@ -2,7 +2,7 @@ import type KnownDevice from '../../../settings/knownDevice.js'; import type { AnyVirtualDevice } from './virtualDevice.js'; type VirtualDeviceFactory = { - create(knownDevice: KnownDevice, provider: string): Promise; + create: (knownDevice: KnownDevice, provider: string) => Promise; }; export default VirtualDeviceFactory; diff --git a/src/device/protocol/virtual/virtualDeviceLogicFactory.ts b/src/device/protocol/virtual/virtualDeviceLogicFactory.ts index 2979364a..f3c50843 100644 --- a/src/device/protocol/virtual/virtualDeviceLogicFactory.ts +++ b/src/device/protocol/virtual/virtualDeviceLogicFactory.ts @@ -2,9 +2,9 @@ import type { ExtractConfig } from './virtualDeviceLogic.js'; import type VirtualDeviceLogic from './virtualDeviceLogic.js'; type VirtualDeviceLogicFactory = { - create(config: ExtractConfig): TDeviceLogic; + create: (config: ExtractConfig) => TDeviceLogic; - forDeviceType(): string; + forDeviceType: () => string; }; export default VirtualDeviceLogicFactory; diff --git a/src/device/protocol/zc95/zc95Device.ts b/src/device/protocol/zc95/zc95Device.ts index 67e5ad6b..fb448d5a 100644 --- a/src/device/protocol/zc95/zc95Device.ts +++ b/src/device/protocol/zc95/zc95Device.ts @@ -33,7 +33,7 @@ type RequiredZc95DeviceAttributes = { patternStarted: InitializedBoolDeviceAttribute; }; -enum Zc95DevicePowerChannelIndex { +export enum Zc95DevicePowerChannelIndex { One = 1, Two = 2, Three = 3, diff --git a/src/device/protocol/zc95/zc95MessageFactory.ts b/src/device/protocol/zc95/zc95MessageFactory.ts index f93c7d4e..ae489fca 100644 --- a/src/device/protocol/zc95/zc95MessageFactory.ts +++ b/src/device/protocol/zc95/zc95MessageFactory.ts @@ -1,3 +1,4 @@ +import type { Zc95DevicePowerChannelIndex } from './zc95Device.js'; import type { Msg, MsgAndResponseIdentifier, MsgResponse } from './zc95Protocol.js'; import Zc95Protocol from './zc95Protocol.js'; @@ -96,7 +97,7 @@ export type PatternsMsgResponse = { } & MsgResponse; type ChannelPowerStatus = { - Channel: 1 | 2 | 3 | 4; + Channel: Zc95DevicePowerChannelIndex; OutputPower: number; MaxOutputPower: number; PowerLimit: number; @@ -146,6 +147,8 @@ export type GetLuaScriptsMsgResponse = { export default class Zc95MessageFactory { + private static readonly MAX_MSG_ID_COUNT = 10000; + private msgId = 0; public createGetPatterns(): MsgAndResponseIdentifier { @@ -324,7 +327,7 @@ export default class Zc95MessageFactory } private getNextMsgIndex(): number { - if (this.msgId >= 10000) { + if (this.msgId >= Zc95MessageFactory.MAX_MSG_ID_COUNT) { this.msgId = 0; } diff --git a/src/device/provider/deviceProviderFactory.ts b/src/device/provider/deviceProviderFactory.ts index 60bfb316..fcc39329 100644 --- a/src/device/provider/deviceProviderFactory.ts +++ b/src/device/provider/deviceProviderFactory.ts @@ -2,6 +2,11 @@ import type { AnyDeviceProvider } from './deviceProvider.js'; import type { JsonObject } from '../../types.js'; type DeviceProviderFactory = { + // TODO Switch to TypeBox schema for each ServiceProvider and adjust factory + // Method syntax: some implementers (e.g. ButtplugIoWebsocketDeviceProviderFactory) + // narrow config to a concrete shape; property syntax would check contravariantly + // and reject that narrowing. + // eslint-disable-next-line @typescript-eslint/method-signature-style create(config: JsonObject): DP; }; diff --git a/src/device/transport/deviceWritableTransport.ts b/src/device/transport/deviceWritableTransport.ts index dbe92257..1df6c941 100644 --- a/src/device/transport/deviceWritableTransport.ts +++ b/src/device/transport/deviceWritableTransport.ts @@ -3,28 +3,28 @@ type DeviceWritableTransport = { * Writes data * @param data The command/input for the device */ - send(data: Buffer): Promise; + send: (data: Buffer) => Promise; /** * Called when the transport is closed (for example when a serial device is unplugged) * @param callback */ - onClose(callback: () => void | Promise): void; + onClose: (callback: () => void | Promise) => void; /** * Returns whether the transport is currently open/active or not */ - isOpen(): boolean; + isOpen: () => boolean; /** * Closes the transport */ - close(): Promise; + close: () => Promise; /** * Returns a deterministic and unique device identifier (for example hardware serial number) */ - getDeviceIdentifier(): string; + getDeviceIdentifier: () => string; }; export default DeviceWritableTransport; From ad6a9c7c6ea238031101e9bb199985be1d1c1267 Mon Sep 17 00:00:00 2001 From: HRS Date: Sat, 8 Aug 2026 23:16:27 +0200 Subject: [PATCH 15/15] Fix and improve eslint config --- eslint.config.ts | 380 +++++++++++++++++++++++------------------------ 1 file changed, 190 insertions(+), 190 deletions(-) diff --git a/eslint.config.ts b/eslint.config.ts index 8744eb7d..e89aaea6 100644 --- a/eslint.config.ts +++ b/eslint.config.ts @@ -1,16 +1,16 @@ -import { defineConfig, globalIgnores } from 'eslint/config'; -import tseslint from 'typescript-eslint'; -import eslint from '@eslint/js'; -import jsdoc from 'eslint-plugin-jsdoc'; -import preferArrowFunctions from 'eslint-plugin-prefer-arrow-functions'; -import globals from 'globals'; -import stylistic from '@stylistic/eslint-plugin'; -import sortClassMembers from 'eslint-plugin-sort-class-members'; +import { defineConfig, globalIgnores } from "eslint/config"; +import tseslint from "typescript-eslint"; +import eslint from "@eslint/js"; +import jsdoc from "eslint-plugin-jsdoc"; +import preferArrowFunctions from "eslint-plugin-prefer-arrow-functions"; +import globals from "globals"; +import stylistic from "@stylistic/eslint-plugin"; +import sortClassMembers from "eslint-plugin-sort-class-members"; export default defineConfig([ - globalIgnores(['**/*.js', '**/*.cjs', '**/*.mjs']), + globalIgnores(["**/*.{js,cjs,mjs}", "!src/**/*.{js,cjs,mjs}"]), { - files: ['src/**/*.ts'], + files: ["src/**/*.ts"], extends: [ eslint.configs.recommended, tseslint.configs.all, @@ -19,287 +19,287 @@ export default defineConfig([ ], languageOptions: { parserOptions: { - project: './tsconfig.json', - sourceType: 'module', + project: "./tsconfig.json", + sourceType: "module", }, globals: { ...globals.node, } }, plugins: { - '@jsdoc': jsdoc, - '@sortClassMembers': sortClassMembers, + "@jsdoc": jsdoc, + "@sortClassMembers": sortClassMembers, }, rules: { // Extremely slow (deep readonly type-checking); can hang lint on this codebase. - '@typescript-eslint/prefer-readonly-parameter-types': 'off', - '@typescript-eslint/no-magic-numbers': ['error', { - 'ignoreReadonlyClassProperties': true, - 'ignoreTypeIndexes': true, - 'ignoreEnums': true, - 'ignoreNumericLiteralTypes': true, - 'ignoreArrayIndexes': true, - 'ignoreDefaultValues': true, - 'ignoreClassFieldInitialValues': true, - 'ignore': [-1, 0, 1, 10], + "@typescript-eslint/prefer-readonly-parameter-types": "off", + "@typescript-eslint/no-magic-numbers": ["error", { + "ignoreReadonlyClassProperties": true, + "ignoreTypeIndexes": true, + "ignoreEnums": true, + "ignoreNumericLiteralTypes": true, + "ignoreArrayIndexes": true, + "ignoreDefaultValues": true, + "ignoreClassFieldInitialValues": true, + "ignore": [-1, 0, 1, 10], }], - '@typescript-eslint/no-confusing-void-expression': [ - 'error', + "@typescript-eslint/no-confusing-void-expression": [ + "error", { - 'ignoreArrowShorthand': true, + "ignoreArrowShorthand": true, } ], // your overrides - '@typescript-eslint/array-type': [ - 'error', + "@typescript-eslint/array-type": [ + "error", { - 'default': 'array' + "default": "array" } ], - '@typescript-eslint/no-restricted-types': [ - 'error', + "@typescript-eslint/no-restricted-types": [ + "error", { - 'types': { - 'Object': 'Avoid using the `Object` type. Did you mean `object`?', - 'Function': 'Avoid using the `Function` type. Prefer a specific function type, like `() => void`.', - 'Boolean': 'Avoid using the `Boolean` type. Did you mean `boolean`?', - 'Number': 'Avoid using the `Number` type. Did you mean `number`?', - 'String': 'Avoid using the `String` type. Did you mean `string`?', - 'Symbol': 'Avoid using the `Symbol` type. Did you mean `symbol`?' + "types": { + "Object": "Avoid using the `Object` type. Did you mean `object`?", + "Function": "Avoid using the `Function` type. Prefer a specific function type, like `() => void`.", + "Boolean": "Avoid using the `Boolean` type. Did you mean `boolean`?", + "Number": "Avoid using the `Number` type. Did you mean `number`?", + "String": "Avoid using the `String` type. Did you mean `string`?", + "Symbol": "Avoid using the `Symbol` type. Did you mean `symbol`?" } } ], - '@typescript-eslint/consistent-type-assertions': [ - 'error', + "@typescript-eslint/consistent-type-assertions": [ + "error", { - 'assertionStyle': 'never', + "assertionStyle": "never", } ], - '@typescript-eslint/explicit-function-return-type': 'error', - '@typescript-eslint/explicit-member-accessibility': 'error', - '@typescript-eslint/explicit-module-boundary-types': 'error', - '@typescript-eslint/naming-convention': [ - 'error', + "@typescript-eslint/explicit-function-return-type": "error", + "@typescript-eslint/explicit-member-accessibility": "error", + "@typescript-eslint/explicit-module-boundary-types": "error", + "@typescript-eslint/naming-convention": [ + "error", { - 'selector': 'classProperty', - 'modifiers': ['static', 'readonly'], - 'format': ['camelCase', 'UPPER_CASE'] + "selector": "classProperty", + "modifiers": ["static", "readonly"], + "format": ["camelCase", "UPPER_CASE"] }, { - 'selector': 'classProperty', - 'format': ['camelCase'], - 'leadingUnderscore': 'allow' + "selector": "classProperty", + "format": ["camelCase"], + "leadingUnderscore": "allow" } ], - '@typescript-eslint/no-floating-promises': [ - 'warn', + "@typescript-eslint/no-floating-promises": [ + "warn", { - 'checkThenables': true + "checkThenables": true } ], - '@typescript-eslint/consistent-type-definitions': [ - 'error', - 'type' + "@typescript-eslint/consistent-type-definitions": [ + "error", + "type" ], - '@typescript-eslint/parameter-properties': 'error', - '@typescript-eslint/no-shadow': 'error', - '@typescript-eslint/no-use-before-define': 'error', - '@typescript-eslint/restrict-template-expressions': [ - 'warn', + "@typescript-eslint/parameter-properties": "error", + "@typescript-eslint/no-shadow": "error", + "@typescript-eslint/no-use-before-define": "error", + "@typescript-eslint/restrict-template-expressions": [ + "warn", { - 'allowNumber': true + "allowNumber": true } ], - '@typescript-eslint/strict-boolean-expressions': 'error', - '@typescript-eslint/class-methods-use-this': [ - 'error', + "@typescript-eslint/strict-boolean-expressions": "error", + "@typescript-eslint/class-methods-use-this": [ + "error", { - 'ignoreOverrideMethods': true, - 'ignoreClassesThatImplementAnInterface': true, + "ignoreOverrideMethods": true, + "ignoreClassesThatImplementAnInterface": true, } ], - '@typescript-eslint/max-params': [ - 'error', + "@typescript-eslint/max-params": [ + "error", { - 'max': 10 + "max": 10 } ], - '@typescript-eslint/prefer-destructuring': 'off', - '@typescript-eslint/member-ordering': 'off', - '@stylistic/indent': ['error', 4], - '@stylistic/semi': ['error', 'always', { 'omitLastInOneLineBlock': true }], - '@stylistic/brace-style': 'off', - '@stylistic/member-delimiter-style': [ - 'error', + "@typescript-eslint/prefer-destructuring": "off", + "@typescript-eslint/member-ordering": "off", + "@stylistic/indent": ["error", 4], + "@stylistic/semi": ["error", "always", { "omitLastInOneLineBlock": true }], + "@stylistic/brace-style": "off", + "@stylistic/member-delimiter-style": [ + "error", { multiline: { - delimiter: 'semi', + delimiter: "semi", requireLast: true, }, singleline: { - delimiter: 'comma', + delimiter: "comma", requireLast: false, }, }, ], - '@stylistic/operator-linebreak': [ - 'error', - 'before', + "@stylistic/operator-linebreak": [ + "error", + "before", { overrides: { - '=': 'after', + "=": "after", }, }, ], - '@stylistic/indent-binary-ops': ['error', 4], - '@stylistic/arrow-parens': ['error', 'as-needed'], - '@stylistic/padding-line-between-statements': ['error', { - blankLine: 'always', - prev: ['type', 'interface', 'class', 'enum'], - next: 'export', + "@stylistic/indent-binary-ops": ["error", 4], + "@stylistic/arrow-parens": ["error", "as-needed"], + "@stylistic/padding-line-between-statements": ["error", { + blankLine: "always", + prev: ["type", "interface", "class", "enum"], + next: "export", }], - '@jsdoc/check-alignment': 'error', - '@jsdoc/check-indentation': 'error', - '@jsdoc/tag-lines': 'error', - '@sortClassMembers/sort-class-members': ['warn', { + "@jsdoc/check-alignment": "error", + "@jsdoc/check-indentation": "error", + "@jsdoc/tag-lines": "error", + "@sortClassMembers/sort-class-members": ["warn", { order: [ - '[public-static-readonly-properties]', - '[protected-static-readonly-properties]', - '[private-static-readonly-properties]', + "[public-static-readonly-properties]", + "[protected-static-readonly-properties]", + "[private-static-readonly-properties]", - '[public-static-properties]', - '[public-properties]', - '[protected-static-properties]', - '[protected-properties]', - '[private-static-properties]', - '[private-properties]', + "[public-static-properties]", + "[public-properties]", + "[protected-static-properties]", + "[protected-properties]", + "[private-static-properties]", + "[private-properties]", - 'constructor', + "constructor", - '[public-static-methods]', - '[public-instance-methods]', - '[public-abstract-methods]', + "[public-static-methods]", + "[public-instance-methods]", + "[public-abstract-methods]", - '[protected-instance-methods]', - '[protected-abstract-methods]', - '[protected-static-methods]', + "[protected-instance-methods]", + "[protected-abstract-methods]", + "[protected-static-methods]", - '[private-instance-methods]', - '[private-static-methods]', // no private-abstract: TS disallows private abstract members + "[private-instance-methods]", + "[private-static-methods]", // no private-abstract: TS disallows private abstract members ], groups: { - 'public-static-readonly-properties': [ - { type: 'property', accessibility: 'public', static: true, readonly: true }, + "public-static-readonly-properties": [ + { type: "property", accessibility: "public", static: true, readonly: true }, ], - 'protected-static-readonly-properties': [ - { type: 'property', accessibility: 'protected', static: true, readonly: true }, + "protected-static-readonly-properties": [ + { type: "property", accessibility: "protected", static: true, readonly: true }, ], - 'private-static-readonly-properties': [ - { type: 'property', accessibility: 'private', static: true, readonly: true }, + "private-static-readonly-properties": [ + { type: "property", accessibility: "private", static: true, readonly: true }, ], - 'public-static-properties': [ - { type: 'property', accessibility: 'public', static: true, readonly: false }, + "public-static-properties": [ + { type: "property", accessibility: "public", static: true, readonly: false }, ], - 'protected-static-properties': [ - { type: 'property', accessibility: 'protected', static: true, readonly: false }, + "protected-static-properties": [ + { type: "property", accessibility: "protected", static: true, readonly: false }, ], - 'private-static-properties': [ - { type: 'property', accessibility: 'private', static: true, readonly: false }, + "private-static-properties": [ + { type: "property", accessibility: "private", static: true, readonly: false }, ], - 'public-properties': [ - { type: 'property', accessibility: 'public', static: false }, + "public-properties": [ + { type: "property", accessibility: "public", static: false }, ], - 'protected-properties': [ - { type: 'property', accessibility: 'protected', static: false }, + "protected-properties": [ + { type: "property", accessibility: "protected", static: false }, ], - 'private-properties': [ - { type: 'property', accessibility: 'private', static: false }, + "private-properties": [ + { type: "property", accessibility: "private", static: false }, ], - 'public-instance-methods': [ - { type: 'method', accessibility: 'public', static: false, abstract: false }, + "public-instance-methods": [ + { type: "method", accessibility: "public", static: false, abstract: false }, ], - 'public-abstract-methods': [ - { type: 'method', accessibility: 'public', abstract: true }, + "public-abstract-methods": [ + { type: "method", accessibility: "public", abstract: true }, ], - 'public-static-methods': [ - { type: 'method', accessibility: 'public', static: true }, + "public-static-methods": [ + { type: "method", accessibility: "public", static: true }, ], - 'protected-instance-methods': [ - { type: 'method', accessibility: 'protected', static: false, abstract: false }, + "protected-instance-methods": [ + { type: "method", accessibility: "protected", static: false, abstract: false }, ], - 'protected-abstract-methods': [ - { type: 'method', accessibility: 'protected', abstract: true }, + "protected-abstract-methods": [ + { type: "method", accessibility: "protected", abstract: true }, ], - 'protected-static-methods': [ - { type: 'method', accessibility: 'protected', static: true }, + "protected-static-methods": [ + { type: "method", accessibility: "protected", static: true }, ], - 'private-instance-methods': [ - { type: 'method', accessibility: 'private', static: false }, + "private-instance-methods": [ + { type: "method", accessibility: "private", static: false }, ], - 'private-static-methods': [ - { type: 'method', accessibility: 'private', static: true }, + "private-static-methods": [ + { type: "method", accessibility: "private", static: true }, ], }, }], - 'complexity': 'error', - 'eqeqeq': [ - 'error', - 'smart' + "complexity": "error", + "eqeqeq": [ + "error", + "smart" ], - 'guard-for-in': 'error', - 'id-denylist': [ - 'error', - 'any', - 'Number', - 'number', - 'String', - 'string', - 'Boolean', - 'boolean', - 'Undefined', - 'undefined' + "guard-for-in": "error", + "id-denylist": [ + "error", + "any", + "Number", + "number", + "String", + "string", + "Boolean", + "boolean", + "Undefined", + "undefined" ], - 'id-match': 'error', - 'max-classes-per-file': 'error', - 'no-bitwise': 'error', - 'no-caller': 'error', - 'no-console': 'warn', - 'no-eval': 'error', - 'no-fallthrough': 'off', - 'no-magic-numbers': 'off', - 'no-new-wrappers': 'error', - 'no-undef-init': 'error', - 'no-underscore-dangle': [ - 'error', + "id-match": "error", + "max-classes-per-file": "error", + "no-bitwise": "error", + "no-caller": "error", + "no-console": "warn", + "no-eval": "error", + "no-fallthrough": "off", + "no-magic-numbers": "off", + "no-new-wrappers": "error", + "no-undef-init": "error", + "no-underscore-dangle": [ + "error", { - 'allowAfterThis': true + "allowAfterThis": true } ], - 'no-implicit-coercion': ['error', { - 'boolean': true, - 'number': true, - 'string': true + "no-implicit-coercion": ["error", { + "boolean": true, + "number": true, + "string": true }], - 'one-var': [ - 'error', - 'never' + "one-var": [ + "error", + "never" ], - 'radix': 'error', + "radix": "error", }, }, { - files: ['src/**/*.js'], + files: ["src/**/*.{js,cjs,mjs}"], rules: { - 'no-restricted-syntax': [ - 'error', + "no-restricted-syntax": [ + "error", { - selector: 'Program', - message: 'JavaScript files are not allowed in this project; use TypeScript (.ts) instead.', + selector: "Program", + message: "JavaScript files are not allowed in this project; use TypeScript (.ts) instead.", }, ], },