]> Piment Noir Git Repositories - e-mobility-charging-stations-simulator.git/commit
feat: resolve #1020 — Persist minimal simulator state and reconstruct template indexe...
authorgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Sun, 17 May 2026 19:14:56 +0000 (21:14 +0200)
committerGitHub <noreply@github.com>
Sun, 17 May 2026 19:14:56 +0000 (21:14 +0200)
commit79a4089c3cbd329e9356096f8a6608d43b539228
treeafc236df693661a364a3036616eefad4c0eac6f4
parent8c973ac9bf8210b85fcecf7f3c01faea3a7d9bde
feat: resolve #1020 — Persist minimal simulator state and reconstruct template indexes on startup (#1858)

* feat: persist minimal simulator state and reconstruct template indexes on startup

- Add persistState boolean to ConfigurationData (default: true)
- Add SimulatorState to FileType enum
- Add simulatorState to AsyncLockType enum
- Implement BootstrapStateUtils with:
  - readStateFile: reads and validates state.json with schema version check
  - writeStateFile: atomic write via tmp+rename with AsyncLock
  - reconstructTemplateIndexes: scans per-station config files to rebuild indexes
  - deleteStateFile: safe file deletion
- Integrate into Bootstrap:
  - reconstructTemplateIndexes called in start() before worker spawn
  - State file written on start() (started:true) and stop() (started:false)
  - shouldAutoStart() reads state file to control auto-start behavior
  - persistStateEnabled getter checks persistState config and SIMULATOR_COLD_START env
- Update start.ts to conditionally start based on shouldAutoStart()
- Handle edge cases: corrupt files, missing fields, incompatible schema version
- Add comprehensive tests for all state persistence and reconstruction scenarios

Closes #1020

* refactor(bootstrap): harmonize persisted state feature with codebase conventions

Address review findings on PR #1858:

- HIGH #1: Phase split start() lifecycle. Add public Bootstrap.startUIServer()
  that always brings up the UI server (and reconstructs template indexes
  before accepting requests). start.ts unconditionally calls startUIServer()
  and only gates Bootstrap.start() on shouldAutoStart(), so a persisted
  stopped state no longer turns the simulator into a zombie process.

- HIGH #2: Add canonical default. New defaultPersistState constant and
  Configuration.getPersistState() accessor matching the existing
  defaultUIServerConfiguration / defaultWorkerConfiguration pattern.
  Bootstrap.persistStateEnabled now delegates instead of inlining ?? true.

- MEDIUM #3: Document persistState tunable and SIMULATOR_COLD_START
  environment override in README.md.

- MEDIUM #4: writeStateFile and deleteStateFile now swallow filesystem
  errors via handleFileException with throwError:false, mirroring
  watchJsonFile and the storage layer. Persistence failures no longer
  surface as misleading 'Startup error' / 'Shutdown error'.

- MEDIUM #5: reconstructTemplateIndexes runs inside startUIServer() before
  uiServer.start(), closing the race where UI requests could arrive before
  index reconstruction completes.

- LOW #7: Add formatLogPrefix() utility and use it in BootstrapStateUtils,
  removing the leading-space artifact when logPrefixFn is undefined.

- LOW #8: On state file schema mismatch, quarantine to <path>.v<N>.bak
  instead of silently deleting, allowing forensics during partial schema
  rollouts. JSON parse errors still delete (true corruption, unrecoverable).

- LOW #9: Move state.json from dist/assets/configurations/ to dist/assets/.
  Drops the fragile basename-based filter from reconstructTemplateIndexes
  and separates control file from per-station configuration files.

Drop shouldAutoStart() from IBootstrap interface (boot-time concern, no UI
service consumer needs it). The method stays public on the concrete
Bootstrap class for start.ts.

Tests: align fixtures with new state.json location, add quarantine assertion,
add non-fatal writeStateFile assertion, drop obsolete state.json filter test.

* test(bootstrap): align BootstrapStateUtils tests with project style guide

- Rename BootstrapState.test.ts to BootstrapStateUtils.test.ts to match the
  source module name (TEST_STYLE_GUIDE.md §1: 'Files: ModuleName.test.ts').
- Add mandatory standardCleanup() in afterEach (TEST_STYLE_GUIDE.md §3),
  matching the convention used by Configuration.test.ts, FileUtils.test.ts,
  ErrorUtils.test.ts and ConfigurationKeyUtils.test.ts.

* fix(bootstrap): address review findings on persisted simulator state

Distinguish UI-initiated stop from signal/restart via a private
StopReason enum, so SIGINT/SIGTERM/SIGQUIT and config-reload restarts
no longer flip the persisted state to stopped (HIGH-1).

Short-circuit persistStateEnabled when the UI server is disabled, since
persistence has no recovery channel without a UI; warn once on the
inconsistent configuration (HIGH-2).

Reconstruct template indexes via a shared prepareTemplateStatistics
helper called from constructor and restart, not only from startUIServer,
to prevent index collisions on config-reload of UI-added stations
(MED-3).

Move state file from dist/assets/state.json (wiped by pnpm build) to
dist/assets/configurations/.simulator-state.json; filter dot-prefixed
metadata files in reconstructTemplateIndexes so the state file co-located
with charging station configurations is silently skipped (MIN-10).

Clean up the .tmp file on atomic-rename failure in writeStateFile and
guard readStateFile against JSON null/primitive/array content with an
explicit message (MIN-7, MIN-8).

Unify path computations into readonly assetsDir/configurationsDir/
stateFilePath fields, deduplicate setChargingStationTemplates via a
syncUIServerTemplates helper, and route the SIMULATOR_COLD_START env
var name through Constants.ENV_SIMULATOR_COLD_START (MIN-5, MIN-6).

Export DEFAULT_PERSIST_STATE and add the persistState entry to
config-template.json so the tunable is discoverable (MED-4).

Update README to document the new state file path, the UI server
requirement, and the signal-shutdown semantics.

Test coverage added for tmp cleanup, JSON null/primitive/array guards,
and dot-prefixed metadata filtering; the misleading 'read-only
directory' test is renamed to reflect the actual OS-rejected-path
scenario (MIN-9).

* docs(bootstrap): clarify persisted state semantics from review feedback

- README: drop developer-internals sentence on template index reconstruction
  from the persistState row
- TemplateStatistics: document the `added` (process-scoped) vs `indexes`
  (process+disk-scoped) distinction exposed via SimulatorState
- IBootstrap: document the contract scope (UI-server-facing, excluding
  process-lifecycle helpers and the internal StopReason)
- formatLogPrefix: document the trailing-space contract
- reconstructTemplateIndexes: refine the warn message wording for non-station
  configuration files (level unchanged)

* refactor(utils): default formatLogPrefix prefix to logPrefix

Avoids silent loss of the timestamp when callers do not pass a module-specific
prefix function. Aligns with the existing convention where every module-level
logPrefix delegates to Utils.logPrefix.

---------

Co-authored-by: Agent <agent@github.com>
Co-authored-by: Jérôme Benoit <jerome.benoit@piment-noir.org>
Co-authored-by: Jérôme Benoit <jerome.benoit@sap.com>
15 files changed:
README.md
src/assets/config-template.json
src/charging-station/Bootstrap.ts
src/charging-station/BootstrapStateUtils.ts [new file with mode: 0644]
src/charging-station/IBootstrap.ts
src/start.ts
src/types/ConfigurationData.ts
src/types/FileType.ts
src/types/Statistics.ts
src/utils/AsyncLock.ts
src/utils/Configuration.ts
src/utils/Constants.ts
src/utils/Utils.ts
src/utils/index.ts
tests/charging-station/BootstrapStateUtils.test.ts [new file with mode: 0644]