mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-28 06:22:45 -08:00
5.1 KiB
5.1 KiB
Architecture
SubMiner uses a service-oriented Electron main-process architecture where src/main.ts acts as the composition root and behavior lives in small runtime services under src/core/services.
Goals
- Keep behavior stable while reducing coupling.
- Prefer small, single-purpose units that can be tested in isolation.
- Keep
main.tsfocused on wiring and state ownership, not implementation detail. - Follow Unix-style composability:
- each service does one job
- services compose through explicit inputs/outputs
- orchestration is separate from implementation
Current Structure
src/main.ts- Composition root for lifecycle wiring and non-overlay runtime state.
- Owns long-lived process state for trackers, runtime flags, and client instances.
- Delegates behavior to services.
src/core/services/overlay-manager-service.ts- Owns overlay/window state (
mainWindow,invisibleWindow, visible/invisible overlay flags). - Provides a narrow state API used by
main.tsand overlay services.
- Owns overlay/window state (
src/core/services/*- Stateless or narrowly stateful units for a specific responsibility.
- Examples: startup bootstrap/ready flow, app lifecycle wiring, CLI command handling, IPC registration, overlay visibility, MPV IPC behavior, shortcut registration, subtitle websocket, jimaku/subsync helpers.
src/core/utils/*- Pure helpers and coercion/config utilities.
src/cli/*- CLI parsing and help output.
src/config/*- Config schema/definitions, defaults, validation, and template generation.
src/window-trackers/*- Backend-specific tracker implementations plus selection index.
src/jimaku/*,src/subsync/*- Domain-specific integration helpers.
Flow Diagram
flowchart TD
Main["src/main.ts\n(composition root)"] --> Startup["runStartupBootstrapRuntimeService"]
Main --> Lifecycle["startAppLifecycleService"]
Lifecycle --> AppReady["runAppReadyRuntimeService"]
Main --> OverlayMgr["overlay-manager-service"]
Main --> Ipc["ipc-service / ipc-command-service"]
Main --> Mpv["mpv-service / mpv-control-service"]
Main --> Shortcuts["shortcut-service / overlay-shortcut-service"]
Main --> RuntimeOpts["runtime-options-ipc-service"]
Main --> Subtitle["subtitle-ws-service / secondary-subtitle-service"]
Main --> Config["src/config/*"]
Main --> Cli["src/cli/*"]
Main --> Trackers["src/window-trackers/*"]
Main --> Integrations["src/jimaku/* + src/subsync/*"]
OverlayMgr --> OverlayWindow["overlay-window-service"]
OverlayMgr --> OverlayVisibility["overlay-visibility-service"]
Mpv --> Subtitle
Ipc --> RuntimeOpts
Shortcuts --> OverlayMgr
Composition Pattern
Most runtime code follows a dependency-injection pattern:
- Define a service interface in
src/core/services/*. - Keep core logic in pure or side-effect-bounded functions.
- Build runtime deps in
main.ts; extract an adapter/helper only when it adds meaningful behavior or reuse. - Call the service from lifecycle/command wiring points.
This keeps side effects explicit and makes behavior easy to unit-test with fakes.
Lifecycle Model
- Startup:
runStartupBootstrapRuntimeServicehandles initial argv/env/backend setup and decides generate-config flow vs app lifecycle start.app-lifecycle-servicehandles Electron single-instance + lifecycle event registration.runAppReadyRuntimeServiceperforms ready-time initialization (config load, websocket policy, tokenizer/tracker setup, overlay auto-init decisions).
- Runtime:
- CLI/shortcut/IPC events map to service calls.
- Overlay and MPV state sync through dedicated services.
- Runtime options and mining flows are coordinated via service boundaries.
- Shutdown:
startAppLifecycleServiceregisters cleanup hooks (will-quit) while teardown behavior stays delegated to focused services frommain.ts.
flowchart LR
Args["CLI args"] --> Bootstrap["runStartupBootstrapRuntimeService"]
Bootstrap -->|generate-config| Exit["exit"]
Bootstrap -->|normal start| AppLifecycle["startAppLifecycleService"]
AppLifecycle --> Ready["runAppReadyRuntimeService"]
Ready --> Runtime["IPC + shortcuts + mpv events"]
Runtime --> Overlay["overlay visibility + mining actions"]
Runtime --> Subsync["subsync + secondary sub flows"]
Runtime --> WillQuit["app will-quit"]
WillQuit --> Cleanup["service-level cleanup + unregister"]
Why This Design
- Smaller blast radius: changing one feature usually touches one service.
- Better testability: most behavior can be tested without Electron windows/mpv.
- Better reviewability: PRs can be scoped to one subsystem.
- Backward compatibility: CLI flags and IPC channels can remain stable while internals evolve.
Extension Rules
- Add behavior to an existing service or a new
src/core/services/*file, not as ad-hoc logic inmain.ts. - Keep service APIs explicit and narrowly scoped.
- Prefer additive changes that preserve existing CLI flags and IPC channel behavior.
- Add/update unit tests for each service extraction or behavior change.
- For cross-cutting changes, extract-first then refactor internals after parity is verified.