src/main/data/services/README.md
Business-logic layer for DataApi: one service per domain, each a direct-import singleton (export const xxxService = new XxxService()) over DbService. Handlers call services; each service owns its table's invariants.
Read DataApi in Main — Implementing Services before adding or changing a service.
| Question | Where |
|---|---|
| Why a singleton and not a lifecycle service? | Lifecycle Decision Guide |
| How do I implement a handler + service? | DataApi in Main |
| Service A needs data owned by service B | Cross-Service Table Access |
| A and B call each other → import cycle | Breaking a circular dependency + dataServiceRegistry.ts |
Concurrent writes / SQLITE_BUSY | Write Serialization |
| Row → Entity mapping, NULL handling | Row → Entity Mapping · utils/ |
order_key / reorder / FTS helpers | utils/ |
Naming (files, Tx suffix, singular/plural) | Naming Conventions |
| Testing a service against a real DB | Database Testing |
| Is this data at all? (DataApi boundary) | DataApi Scope & Boundaries |
export const xxxService = new XxxService(); no getInstance(), no new at call sites.tx); cross-service reads may inline a JOIN.getDataService('X'). Every other service stays a plain singleton; never await import to break a cycle.application.get('DbService').withWriteTx(...) is the conventional wrapper (a direct db.transaction(...) is equivalent under the single synchronous connection). A single autocommit write needs neither.application.getPath(...) and loggerService.withContext(...); never ad-hoc.