Skip to content

refactor(#276): extract QueryExecutionService — phases 0–1#278

Merged
BorisTyshkevich merged 1 commit into
mainfrom
refactor/app-services-276
Jul 17, 2026
Merged

refactor(#276): extract QueryExecutionService — phases 0–1#278
BorisTyshkevich merged 1 commit into
mainfrom
refactor/app-services-276

Conversation

@BorisTyshkevich

Copy link
Copy Markdown
Collaborator

What & why

Part of #276 — phases 0–1 of the app.ts → application-services refactor (plan and considered API deviations recorded in the issue's first comment; roadmap #68 carries the per-phase track).

Phase 0 — guard + baseline. New build/check-boundaries.mjs (zero-dependency import walker, wired into pretest as check:arch) enforces the day-1 architecture rule: src/application/** must not import src/ui/** or src/editor/** (type-only imports count). Later phases extend the rule set. The issue's required characterization coverage was audited and already exists (app.test.ts / dashboard.test.ts: streaming run, explicit FORMAT, cancel + partial results, the script retry matrix incl. the verbatim "may have executed" warning, Basic + OAuth refresh, both export cancellations, schema-graph stale-write protection, save/share validation) — no re-adds needed. Metrics per the issue: app.ts 2964 → 2878 lines; App contract ~189 members, runReadInto removed, one nested exec member added.

Phase 1 — QueryExecutionService. src/application/query-execution-service.ts (264 lines, 100/97.87/100/100 per-file coverage) now owns the shared request/stream/normalize core (formerly app.runReadInto) and runScript's per-statement transport loop: SESSION_IS_LOCKED / transient-read-only retry classification, stop-on-first-failure, and a fresh query_id per attempt published synchronously via onStatementStart so Cancel's KILL QUERY always targets the live statement. Constructible without App/AppState/DOM; every side effect injected ({runQuery, killQuery, ctx, now, uid, retryMs, sleep, sqlString}). Behavior, wire format, retry rules, and error strings are byte-identical.

All four callers migrated in one pass, app.runReadInto deleted (no compatibility shim): workbench run()/runScript(), dashboard tiles + filter sources, and the detached Data view use app.exec.executeRead/executeScript; cancel() uses app.exec.kill(queryId). fake-app plus ~70 test-mock sites renamed to the exec shape.

Considered deviations (details in the issue comment):

  • kill(queryId) instead of the suggested cancel(operationId) registry — cancellation stays caller-owned (AbortController + generation counters), per the issue's own rule 5; a registry would add long-lived mutable service state nobody consumes.
  • The service's dependency bag deliberately excludes wallNow — the F6 wave-clock invariant (params snapshot before auth awaits) stays caller-side and cannot move by accident.

Type tightening that fell out of review: ResultSortcore/sort.ts, ScriptEntrycore/script-result.ts (old import paths re-export — zero importer churn); filterExecution/panelExecution params bags and tab.chSession now carry their real wire types (Record<string, string | number> / string), deleting every as-cast at the execution call sites.

Verification: full gate green (103 files / 2940 tests + tsc + check:arch), npm run build green, e2e chromium + webkit fully green, firefox green serialized (--workers=1; the parallel-run timeouts are this machine's documented environment flake).

Acceptance criteria this PR completes: QueryExecutionService constructible/tested without App/AppState/DOM ✅; workbench, dashboard tiles, and detached reads share one execution service ✅; src/application/** has no src/ui/** imports (enforced) ✅.

Checklist

  • npm test passes (the per-file coverage gate is non-negotiable)
  • Tests added/updated in the same change as the code
  • npm run build succeeds (single-file dist/sql.html)
  • Layers kept honest: pure logic in src/core/, network in src/net/ (injected fetch), DOM in src/ui/
  • No new runtime dependency (or it's a deliberate, justified addition — see CONTRIBUTING)
  • README / CHANGELOG.md ([Unreleased]) updated if behavior or the deployed surface changed
  • Reconciled affected tracked work (roadmap Roadmap to 1.0.0 #68, the issue body, ADR/CHANGELOG) if this change reshaped it

🤖 Generated with Claude Code

Phase 0: architecture guard build/check-boundaries.mjs (wired into pretest
as check:arch) enforcing the day-1 rule — src/application/** must not
import src/ui/** or src/editor/** (type-only imports count). Grows with
later #276 phases. Characterization coverage was audited against the
issue's Phase-0 list and already exists in tests/unit/app.test.ts and
dashboard.test.ts (streaming, explicit FORMAT, cancel+partial, script
retry matrix incl. the "may have executed" warning, Basic/OAuth refresh,
export cancellation, schema-graph stale-write, save/share validation).

Phase 1: src/application/query-execution-service.ts — the shared
request/stream/normalize core (formerly app.runReadInto) plus runScript's
per-statement transport loop (SESSION_IS_LOCKED / transient-read-only
retry classification, stop-on-first-failure, fresh query_id per attempt
published synchronously via onStatementStart so Cancel's KILL QUERY
always targets the live statement). Constructible without App/AppState/
DOM; deps injected ({runQuery, killQuery, ctx, now, uid, retryMs, sleep,
sqlString}) — deliberately no wallNow (the F6 wave-clock invariant stays
caller-side) and deliberately kill(queryId) instead of the issue's
cancel(operationId) registry (cancellation stays caller-owned; see the
issue comment). Behavior, wire format, retry rules, and error strings
are byte-identical.

All four callers migrated in one pass and app.runReadInto deleted:
run()/runScript() (app.ts), dashboard tiles + filter sources
(dashboard.ts), detached Data view (results.ts) → app.exec.executeRead /
executeScript; cancel() → app.exec.kill. fake-app + ~70 test-mock sites
renamed to the exec shape.

Type homes tightened along the way: ResultSort → core/sort.ts and
ScriptEntry → core/script-result.ts (old paths re-export, zero importer
churn); filterExecution/panelExecution params bags and tab.chSession now
carry their real wire types, deleting every as-cast at the execution
call sites.

Metrics (issue Phase-0 requirement): app.ts 2964 → 2878 lines; the App
contract loses runReadInto and gains one nested exec member; the new
service is 264 lines at 100/97.87/100/100 per-file coverage.

Gate: npm test (103 files / 2940 tests) green, npm run build green,
e2e chromium+webkit fully green, firefox green serialized (--workers=1;
the parallel-run timeouts are the documented environment flake).

Part of #276 (phases 0-1 of 5; roadmap #68 updated).

Claude-Session: ad39bf19-1690-43c7-abb4-f0084fca00a2

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant