You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
src/settings/settings.ts currently defines SettingsSchema (a typebox schema) alongside a Settings class decorated with class-transformer (@Expose, @Transform(createMapTransformFn(...))). SettingsManager loads settings via PlainToClassSerializer.transform(Settings, plainJsonSettings, SettingsSchema) (ajv-validates against SettingsSchema, then uses class-transformer's plainToInstance to build a Settings instance with Map-typed fields) and saves via ClassToPlainSerializer.transform(this.settings) (instanceToPlain, converting the Maps back to plain objects).
We recently introduced parseAndValidateJson() (src/util/json.ts) — a reusable helper that parses a raw JSON string and validates it against a typebox/ajv JsonSchemaValidator<T> schema in one step, returning Static<T> (already used by Zc95Protocol.decode()). We'd like SettingsManager to use this same pattern instead of going through class-transformer.
Proposal
Change Settings from a class-transformer-decorated class into a plain type: type Settings = Static<typeof SettingsSchema>. This makes knownDevices/deviceSources plain Record<string, ...> objects (as the schema already describes) instead of Map<string, KnownDevice> / Map<string, DeviceSource>.
Move all the current Settings class methods (getDeviceSources(), getKnownDevices(), getKnownDevicesBySource(), getKnownDeviceById(), addKnownDevice(), addDeviceSource()) out of the class and into SettingsManager (or standalone functions operating on the plain Settings data), reimplemented against Record/Object.entries()/Object.values() instead of Map methods.
SettingsManager.load() / handleExternalChange(): replace plainToClassSerializer.transform(Settings, plainJsonSettings, SettingsSchema) with parseAndValidateJson(fileContent, settingsValidator) (a JsonSchemaValidator<typeof SettingsSchema>, obtained via JsonSchemaValidatorFactory).
SettingsManager.save(): replace classToPlainSerializer.transform(this.settings) with a plain JSON.stringify(this.settings, null, 4), since the in-memory representation would already be plain JSON-shaped data — no serialization step needed.
This would let createMapTransformFn (src/util/createMapTransformFn.ts) be removed entirely, if nothing else in the codebase still needs it — it's explicitly marked as a workaround (/* eslint-disable */, comment linking to ES6 Maps are not constructed properly typestack/class-transformer#288) for bridging Map fields through class-transformer, which this refactor would make unnecessary for Settings.
Known blast radius (found via a full-repo search, needs to be addressed as part of this change)
Callers of Settings's current class methods that will need updating to work against the plain-object shape instead:
src/settings/settingsManager.ts:206 — settings.addDeviceSource(...) (in getDefaultSettings())
Serialization path that also depends on Settings being a class-transformer class:
src/app.ts:132 — io.emit(SettingsEventType.changed, serializer.transform<SerializedSettings>(settings)), using ClassToPlainSerializer. Need to check whether SerializedSettings (src/settings/serializedTypes.ts:20) differs meaningfully from Static<typeof SettingsSchema> (field renaming/exclusion via decorators) — if not, this could simplify to emitting settings directly with no serializer involved.
Open questions to resolve during implementation
Where should the relocated logic (getKnownDeviceById, addKnownDevice, etc.) live — as SettingsManager methods, or as standalone exported functions taking settings: Settings as a parameter? Affects whether callers change their call sites minimally (settingsManager.getKnownDeviceById(...)) or more (getKnownDeviceById(settings, ...)).
KnownDevice and DeviceSource (currently also presumably class-transformer classes, referenced by createMapTransformFn(KnownDevice) / createMapTransformFn(DeviceSource)) — do they also need to become plain types derived from the schema, or can they stay as classes constructed manually from the validated plain records?
Confirm SerializedSettings vs Static<typeof SettingsSchema> shape parity before simplifying the app.ts WebSocket emit path.
Context
src/settings/settings.tscurrently definesSettingsSchema(a typebox schema) alongside aSettingsclass decorated withclass-transformer(@Expose,@Transform(createMapTransformFn(...))).SettingsManagerloads settings viaPlainToClassSerializer.transform(Settings, plainJsonSettings, SettingsSchema)(ajv-validates againstSettingsSchema, then usesclass-transformer'splainToInstanceto build aSettingsinstance withMap-typed fields) and saves viaClassToPlainSerializer.transform(this.settings)(instanceToPlain, converting theMaps back to plain objects).We recently introduced
parseAndValidateJson()(src/util/json.ts) — a reusable helper that parses a raw JSON string and validates it against a typebox/ajvJsonSchemaValidator<T>schema in one step, returningStatic<T>(already used byZc95Protocol.decode()). We'd likeSettingsManagerto use this same pattern instead of going throughclass-transformer.Proposal
Settingsfrom aclass-transformer-decorated class into a plain type:type Settings = Static<typeof SettingsSchema>. This makesknownDevices/deviceSourcesplainRecord<string, ...>objects (as the schema already describes) instead ofMap<string, KnownDevice>/Map<string, DeviceSource>.Settingsclass methods (getDeviceSources(),getKnownDevices(),getKnownDevicesBySource(),getKnownDeviceById(),addKnownDevice(),addDeviceSource()) out of the class and intoSettingsManager(or standalone functions operating on the plainSettingsdata), reimplemented againstRecord/Object.entries()/Object.values()instead ofMapmethods.SettingsManager.load()/handleExternalChange(): replaceplainToClassSerializer.transform(Settings, plainJsonSettings, SettingsSchema)withparseAndValidateJson(fileContent, settingsValidator)(aJsonSchemaValidator<typeof SettingsSchema>, obtained viaJsonSchemaValidatorFactory).SettingsManager.save(): replaceclassToPlainSerializer.transform(this.settings)with a plainJSON.stringify(this.settings, null, 4), since the in-memory representation would already be plain JSON-shaped data — no serialization step needed.createMapTransformFn(src/util/createMapTransformFn.ts) be removed entirely, if nothing else in the codebase still needs it — it's explicitly marked as a workaround (/* eslint-disable */, comment linking to ES6 Maps are not constructed properly typestack/class-transformer#288) for bridgingMapfields throughclass-transformer, which this refactor would make unnecessary forSettings.Known blast radius (found via a full-repo search, needs to be addressed as part of this change)
Callers of
Settings's current class methods that will need updating to work against the plain-object shape instead:src/device/deviceManager.ts:72—this.settingsManager.getSettings()?.getKnownDeviceById(deviceId)src/device/knownDeviceRegistry.ts:22,40,44—this.settings.getKnownDeviceById(...),this.settings.addKnownDevice(...)src/device/protocol/virtual/virtualDeviceProvider.ts:75—settings.getKnownDevicesBySource(...)src/device/provider/deviceProviderManager.ts:49—settings.getDeviceSources()src/settings/settingsManager.ts:206—settings.addDeviceSource(...)(ingetDefaultSettings())Serialization path that also depends on
Settingsbeing aclass-transformerclass:src/app.ts:132—io.emit(SettingsEventType.changed, serializer.transform<SerializedSettings>(settings)), usingClassToPlainSerializer. Need to check whetherSerializedSettings(src/settings/serializedTypes.ts:20) differs meaningfully fromStatic<typeof SettingsSchema>(field renaming/exclusion via decorators) — if not, this could simplify to emittingsettingsdirectly with no serializer involved.Open questions to resolve during implementation
getKnownDeviceById,addKnownDevice, etc.) live — asSettingsManagermethods, or as standalone exported functions takingsettings: Settingsas a parameter? Affects whether callers change their call sites minimally (settingsManager.getKnownDeviceById(...)) or more (getKnownDeviceById(settings, ...)).KnownDeviceandDeviceSource(currently also presumably class-transformer classes, referenced bycreateMapTransformFn(KnownDevice)/createMapTransformFn(DeviceSource)) — do they also need to become plain types derived from the schema, or can they stay as classes constructed manually from the validated plain records?SerializedSettingsvsStatic<typeof SettingsSchema>shape parity before simplifying theapp.tsWebSocket emit path.