feat(#630): create @altinity/clickhouse-http, move low-level request/URL (phase 2) - #641
Conversation
Introduce the repository's first npm workspace, packages/clickhouse-http, as a statement-free private package with no production imports yet: root package.json declares the workspace + a 0.0.0 dependency on @altinity/clickhouse-http, package-lock.json is regenerated via npm install (never hand-edited), tsconfig.json includes the package source, the Dockerfile copies packages/ into the build stage before npm ci, and the CI path filters add packages/** to the unit/build/bundle/e2e/docker classifications. Verified npm ci, the full six-command gate, and a real docker build are green at this checkpoint before any runtime cutover. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…altinity/clickhouse-http (Checkpoint 2) Implement packages/clickhouse-http/src/url.ts (chUrl, moved verbatim) and client.ts (createClickHouseHttpClient/request(), the direct injected-fetch invocation) behind the package's public "." export, and cut over src/net/clickhouse-http-transport.ts to a temporary compatibility adapter whose send() delegates to the package's request() — streamLines() stays local, deferred to Phase 3. src/net/clickhouse-transport.types.ts now aliases TransportDeps/TransportRequest from the package's own types, and ch-client.ts binds its chUrl re-export to the package while keeping its eager pre-credential preflight call and composition graph unchanged. Architecture: build/check-boundaries.mjs gains Rules A-D (package must not import root src/**; package source has zero bare specifiers — an empty allowlist, since root hoists real runtime deps like @preact/signals-core that TS/esbuild could otherwise resolve; root src/** must not deep-import the package's own src/**; the bare package name is importable only under src/net/**, and its deep-import subpath form is banned everywhere), mirrored in tests/unit/clickhouse-http-package-policy.test.js with non-writing virtual- file sabotage probes for each rule plus a drift-bind check against the production RULES source. Tests: the exact chUrl literal suite and the Phase-1 contract suite move to tests/unit/clickhouse-http-package.test.ts, registered directly against the package's own request() (not just the compatibility adapter) — proving native Response identity, exactly-one-Fetch, exact SQL, opaque Authorization, live origin/fetch, and raw-byte safety hold at the package boundary. New focused tests pin the reviewed promise-settlement shape: a synchronous chUrl URIError must surface as a rejected promise, never a synchronous throw, at both the package request() and the compatibility adapter's send(). Bundle attribution treats packages/** as first-party project code (not "other"), proven against the real esbuild metafile. All 8 known raw-ESM e2e fixtures gain an import-map entry for the bare @altinity/clickhouse-http specifier; a full esbuild-transform-accurate dependency-graph audit of every other e2e fixture (which also excludes type-only `import type` chains erased at serve time, unlike a naive text scan) found no additional fixture needing one. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ickhouse-http package Document the Phase 2 extraction across the repository's canonical architecture references: CLAUDE.md hard rules 2 and 4 now distinguish SQL Browser network integration/application policy (src/net/**) from reusable ClickHouse HTTP/Fetch mechanics (packages/clickhouse-http), name the package in the repo map, and clarify it as first-party project source rather than an eighth bundled runtime dependency. docs/ARCHITECTURE.md's transport-seam section and layer diagram now describe the package/adapter split and its architecture-enforced boundaries. docs/ADR-0005-clickhouse-web-client.md gains a new addendum noting #630 extracts SQL Browser's own proven Fetch-native mechanics into a first-party package — independent of, and not reopening, the Rejected @clickhouse/client-web decision — without rewriting any historical official-client evidence. .wiki/Source-Map.md and .wiki/Decisions-and-Roadmap.md are updated to match. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
ChatGPT review pass 1Reviewed head: Required change — Major: the architecture scanner does not fully enforce the claimed dynamic-import boundary
import(`@preact/signals-core`)produces no extracted specifier, so Rule B does not report the bare package import. The same bypass applies to the other rules when the template-literal target is a forbidden relative root/deep path or The policy mirror copies the same four regexes, so it shares the blind spot rather than independently catching it. Its sabotage probes currently use static imports; they do not pin the dynamic/re-export forms named by the acceptance question. Action: make dynamic-import handling fail closed. At minimum recognize no-substitution template literals; stronger is to reject any This is an acceptance blocker even though the current package tree is clean: the PR claims the zero-bare-specifier/package-boundary rules are mechanically enforced and sabotage-tested. A valid JavaScript spelling that bypasses both the production gate and its mirror makes that enforcement claim false. Other adversarial checksNo additional blocking defect found. The new Checkpoint 1 ( The raw-ESM footprint is consistent with the stated eight fixtures: seven fixtures load
VERDICT: REVISE |
ChatGPT review pass 2Previously reviewed head: There is no code delta between review passes: GitHub reports the two SHAs as identical (0 commits, 0 changed files). This pass therefore reassesses the pass-1 finding against repository history/scope and re-checks the complete unchanged PR for regressions. Pass-1 finding reassessmentDynamic-import scanner gap — real, but not a merge blocker for this PR. I re-confirmed the gap against the exact current regex: static imports, The strongest objection is that Rule B's prospective "zero bare specifiers" promise is new even if its parser helper is old. If the DoD required language-complete parsing of every valid JavaScript import spelling, the pass-1 blocker would stand. I do not read A2/A4/A5 or this repository's explicitly regex-scoped checker contract that way: the required current package boundary is clean, and this PR does not introduce or rely on an unsupported dynamic-import spelling. Checkpoint-1 CI evidence — not a defect. Checkpoint 1 ( WebKit reproduction scope — not a defect. The repository's PR workflow explicitly runs Chromium-only Playwright on pull requests; WebKit is part of nightly/manual/release coverage, not the PR gate. The required head CI run is green. Complete PR regression re-checkNo new regression was introduced between passes because the head did not move. Re-inspection of the full 34-file PR still finds no additional blocker:
No merge-blocking finding remains for the claimed Phase-2 acceptance subset. VERDICT: SHIP |
What & why
Part of #630 — replacing the rejected
@clickhouse/client-webadoption path (#585) witha first-party, Fetch-native
@altinity/clickhouse-httppackage extracted from genericprotocol code SQL Browser already maintains.
This is Phase 2 of 8. It creates the repository's first npm workspace,
packages/clickhouse-http, and moveschUrl()/URL serialization plus the low-levelFetch request into it.
src/net/clickhouse-http-transport.ts'ssend()becomes a thindelegating compatibility adapter; stream reading (
streamLines()) and all SQL Browserauth/epoch/lifecycle/retry policy stay local until later phases.
Claims: A2 (
@altinity/clickhouse-httpexists in-repo with no SQL Browser sourceimports), A4 (SQL Browser consumes the package through its public export), A5 (URL
serialization has exactly one implementation, owned by the package). A6–A18 remain
deferred to later phases.
Approved plan: see the ship-log comment on #630. Plan review took an unusual path this
round — Agent Chrome disconnected mid-loop during the automated ChatGPT-author/Fable
loop; the recovery (reading the completed ChatGPT response directly from the DOM and
invoking Fable directly, bypassing the
chatgpt-reviewskill/Playwright) is documentedin full in the ship-log's Phase 2 handoff. Fable/high approved on the 3rd real review
pass after 3 findings were incorporated (a Docker build break, a zero-runtime-deps
enforcement gap, and an async/sync settlement drift — all three verified fixed by an
independent Fable re-review).
Contract coverage
check-boundaries.mjsRule Aclient.tsimporting../../../src/net/ch-client.js→ caughtcheck-boundaries.mjsRule Curl.tsimporting@preact/signals-core→ caught bycheck:arch(verified independently)@altinity/clickhouse-httprestricted tosrc/net/**src/core/**→ caughtDockerfileCOPY packages ./packagesbeforenpm cidocker buildrequest()tests/unit/clickhouse-http-package.test.tsfunction chUrl(definition, in the packagesrc/**+packages/**request()/send()both stayasync, preserving today's rejected-promise contract for malformed URLsTests
npm run check:types && npm run check:arch && npm run check:schemas && npm run check:examples && npm test && npm run build— green (100%/97.14%/100%/100% coverage, including the 3 new package files, verified via lcov).npm ci --no-audit --no-fund— green;node_modules/@altinity/clickhouse-httpresolves as a real local workspace symlink, not a registry package.npm run test:e2e -- tests/e2e/clickhouse-http-transport.spec.js --project=chromium --project=webkit— 18/18 passed, now through the package-backed compatibility adapter.--project=chromium --project=webkit— 432 passed, 4 skipped (pre-existing, unrelated).docker build(legacy non-buildx driver — buildx blocked in this sandbox) — succeeds; independently re-verified it fails exactly as expected when theCOPY packages ./packagesfix is removed.import typeerasure, once by a separate reviewer) confirming no additional fixture needs one.Build
npm run buildsucceeds — single self-containeddist/sql.html, no new runtimedependency (the workspace package is first-party project code, not a third-party
dependency — CLAUDE.md hard rule 4 clarified accordingly).
Invariant/sabotage verification
Verified three times: once by the implementer, once by the coordinator directly
(diff footprint,
npm cilinkage, full gate, lcov coverage, the bare-specifier andDocker sabotages, e2e), and once by a separate targeted read-only review covering the
remaining invariant-map items (import-ban sabotages, move-not-duplicate test claims,
ch-client.tscomposition,streamLines()locality, single-serializer invariant,direct-package contract registration) — no defects found in any pass.
Checklist
npm testpasses (the per-file coverage gate is non-negotiable)npm run buildsucceeds (single-filedist/sql.html)CHANGELOG.md([Unreleased]) updatedCLAUDE.md,docs/ARCHITECTURE.md, ADR-0005 addendum, and.wikipages updated per the plan's Checkpoint 3; the issue's own## Phaseschecklist is ticked only after merge is verified onorigin/main, per its explicit stated conventionPart of #630.