Skip to content

Refactor Settings: move logic out of settings.ts, load/save via parseAndValidateJson instead of class-transformer #105

Description

@heavyrubberslave

Context

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/device/deviceManager.ts:72this.settingsManager.getSettings()?.getKnownDeviceById(deviceId)
  • src/device/knownDeviceRegistry.ts:22,40,44this.settings.getKnownDeviceById(...), this.settings.addKnownDevice(...)
  • src/device/protocol/virtual/virtualDeviceProvider.ts:75settings.getKnownDevicesBySource(...)
  • src/device/provider/deviceProviderManager.ts:49settings.getDeviceSources()
  • src/settings/settingsManager.ts:206settings.addDeviceSource(...) (in getDefaultSettings())

Serialization path that also depends on Settings being a class-transformer class:

  • src/app.ts:132io.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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions