Back to Cherry Studio

Data Services

src/main/data/services/README.md

2.0.02.8 KB
Original Source

Data Services

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.

Key questions → docs

QuestionWhere
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 BCross-Service Table Access
A and B call each other → import cycleBreaking a circular dependency + dataServiceRegistry.ts
Concurrent writes / SQLITE_BUSYWrite Serialization
Row → Entity mapping, NULL handlingRow → Entity Mapping · utils/
order_key / reorder / FTS helpersutils/
Naming (files, Tx suffix, singular/plural)Naming Conventions
Testing a service against a real DBDatabase Testing
Is this data at all? (DataApi boundary)DataApi Scope & Boundaries

Local conventions (quick)

  • Singleton, not lifecycleexport const xxxService = new XxxService(); no getInstance(), no new at call sites.
  • Own your table — writes to a table you don't own go through the owner's method (pass tx); cross-service reads may inline a JOIN.
  • Cross-service cyclesonly the services in a real cycle join the registry: they self-register and resolve the sibling via getDataService('X'). Every other service stays a plain singleton; never await import to break a cycle.
  • Transactions — wrap multi-statement / read-then-write mutations in a transaction for atomicity; 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.
  • Paths & loggingapplication.getPath(...) and loggerService.withContext(...); never ad-hoc.