mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-27 18:22:41 -08:00
116 lines
5.1 KiB
Markdown
116 lines
5.1 KiB
Markdown
# 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.ts` focused 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.ts` and overlay services.
|
|
- `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
|
|
|
|
```mermaid
|
|
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:
|
|
|
|
1. Define a service interface in `src/core/services/*`.
|
|
2. Keep core logic in pure or side-effect-bounded functions.
|
|
3. Build runtime deps in `main.ts`; extract an adapter/helper only when it adds meaningful behavior or reuse.
|
|
4. 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:
|
|
- `runStartupBootstrapRuntimeService` handles initial argv/env/backend setup and decides generate-config flow vs app lifecycle start.
|
|
- `app-lifecycle-service` handles Electron single-instance + lifecycle event registration.
|
|
- `runAppReadyRuntimeService` performs 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:
|
|
- `startAppLifecycleService` registers cleanup hooks (`will-quit`) while teardown behavior stays delegated to focused services from `main.ts`.
|
|
|
|
```mermaid
|
|
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 in `main.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.
|