Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,17 @@ jobs:
- 'package.json'
- 'package-lock.json'
- '.github/workflows/**'
# #630 Phase 2: the first npm workspace — its source and
# manifest are unit-test/typecheck inputs too.
- 'packages/**'
build:
- 'src/**'
- 'schemas/**'
- 'build/**'
- 'package.json'
- 'package-lock.json'
- '.github/workflows/**'
- 'packages/**'
bundle:
- 'src/**'
- 'schemas/**'
Expand All @@ -70,6 +74,7 @@ jobs:
- 'package.json'
- 'package-lock.json'
- '.github/workflows/**'
- 'packages/**'
# #564: the browser suite is the ONLY gate that can see CSS layout,
# container queries, real focus/hover, and IndexedDB — happy-dom sees
# none of it. Anything that can move rendered geometry or the
Expand All @@ -91,6 +96,7 @@ jobs:
- 'package.json'
- 'package-lock.json'
- '.github/workflows/**'
- 'packages/**'
docker:
- 'Dockerfile'
- 'deploy/caddy/**'
Expand All @@ -101,6 +107,7 @@ jobs:
- 'package.json'
- 'package-lock.json'
- '.github/workflows/**'
- 'packages/**'

# Example-only PRs still validate every portable bundle, authored Dashboard,
# filter contract, and generator normalization without paying for coverage,
Expand Down
24 changes: 24 additions & 0 deletions .wiki/Decisions-and-Roadmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,30 @@ Two roadmap tracks are current:
fetch, or a deliberate renegotiation of the transport contract's
cancellation semantics themselves.

- **#630 — extract the SQL Browser's own Fetch-native transport mechanics
into a first-party package.** Independent of the #585/ADR-0005 track above
(ADR-0005 rejects the *third-party* `@clickhouse/client-web` client; #630
extracts SQL Browser's *own* hand-rolled, already-proven-correct
mechanics, and does not reopen or depend on ADR-0005's decision either
way). Phase 1 (merged, PR #640) froze the current `createHttpTransport`'s
native Fetch/Response/cancellation semantics as a real-browser
characterization suite, with no production code change — the behavioral
baseline Phase 2 is not allowed to alter. **Phase 2** moves `chUrl`/URL
serialization and the low-level injected-`fetch()` request into a new
`packages/clickhouse-http` — the repository's first npm workspace,
private, zero runtime dependencies, zero bare-specifier imports of its
own, exposing only its `.` export — and turns
`src/net/clickhouse-http-transport.ts` into a temporary compatibility
adapter whose `send()` delegates to the package's `request()`;
`streamLines()` stays local, deferred to a later phase. `ch-client.ts`'s
composition graph, auth/epoch/retry policy, and eager pre-credential
`chUrl` preflight are all unchanged. See
[[Source-Map]] and [[Architecture]] for the file-level detail and
`build/check-boundaries.mjs`'s Rules A–D for the mechanical boundary
enforcement (package↔root-src ban, package zero-bare-specifier ban,
root↔package-deep-import ban, bare-import location restricted to
`src/net/**`).

Re-read GitHub before acting because issue state can change; a MERGED PR is
not proof its code is on `main` (see the reset above).

Expand Down
7 changes: 4 additions & 3 deletions .wiki/Source-Map.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@ Back to [[Home]]. Related: [[Architecture]], [[Product-and-Features]].
| `src/dashboard/application/dashboard-repaint-plan.js` | pure repaint-decision arbitration extracted from `ui/dashboard.js`'s `renderDashboard` effect (#589) |
| `src/ui/dashboard-tile-gestures.js` | Dashboard corner-drag resize, Command/Ctrl-drag reorder, and modifier-cue controller, extracted from `ui/dashboard.js` behind an injected `TileGestureDeps` seam (#589) |
| `src/state.js` | signals-backed state model and persistence operations |
| `src/net/ch-client.js` | ClickHouse HTTP execution and schema calls; auth/epoch/retry policy, product operations, `ChCtx` (#585 Phase 1: generic request/stream mechanics delegate through the transport seam below) |
| `src/net/clickhouse-transport.types.js` | Type-only `ClickHouseTransport` contract (`send`/`streamLines`, `TransportDeps`, `TransportRequest`) (#585 Phase 1) |
| `src/net/clickhouse-http-transport.js` | `createHttpTransport` — the current custom HTTP implementation of that contract, plus `chUrl`/`ChUrlOpts` (#585 Phase 1) |
| `src/net/ch-client.js` | ClickHouse HTTP execution and schema calls; auth/epoch/retry policy, product operations, `ChCtx` (#585 Phase 1: generic request/stream mechanics delegate through the transport seam below; #630 Phase 2: `chUrl` re-exported from `@altinity/clickhouse-http`) |
| `src/net/clickhouse-transport.types.js` | Type-only `ClickHouseTransport` contract (`send`/`streamLines`); `TransportDeps`/`TransportRequest` alias the package's own types (#585 Phase 1; #630 Phase 2) |
| `src/net/clickhouse-http-transport.js` | `createHttpTransport` — temporary compatibility adapter: `send()` delegates to `@altinity/clickhouse-http`'s `request()`; `streamLines()` (progress-bearing JSON-lines loop) stays local (#585 Phase 1; #630 Phase 2) |
| `packages/clickhouse-http/src/` | First-party npm workspace package (repo's first) — `url.ts` (`chUrl`, the ONE URL-serializer implementation), `client.ts` (`createClickHouseHttpClient`, the low-level request/Fetch invocation); public export only, zero runtime dependencies, zero bare-specifier imports, no SQL Browser `src/**` dependency (#630 Phase 2) |
| `src/net/oauth.js` | OAuth flow/token exchange |
| `src/editor/editor-port.js` | SQL editor contract and safe no-op port |
| `src/editor/codemirror-adapter.js` | SQL CodeMirror 6 adapter |
Expand Down
32 changes: 32 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,38 @@ auto-generated per-PR notes; this file is the curated, human-readable history.
## [Unreleased]

### Added
- **#630 Phase 2: create `@altinity/clickhouse-http`, the repository's first npm
workspace package, and move low-level request/URL mechanics into it.**
`packages/clickhouse-http` now owns `chUrl()`/URL serialization and a
low-level `createClickHouseHttpClient(deps).request(request)` — a direct,
branch-free `native fetch()` passthrough preserving exact SQL/Authorization,
live `origin()`/`fetch()` accessors, the caller's original `AbortSignal`,
and native `Response` identity (all nine Phase-1 invariants now pass
directly against the package's own `request()`, not only through the
compatibility path). `src/net/clickhouse-http-transport.ts`'s `send()`
becomes a thin delegating adapter to the package; `streamLines()` and all
SQL Browser auth/epoch/lifecycle/retry policy remain unchanged and local.
`ch-client.ts`'s composition graph (`ChCtx -> transportFor -> createHttpTransport
-> send`) is untouched; its eager pre-credential `chUrl()` validation now
calls the package's serializer. Root `package.json` gains a `workspaces`
entry and an explicit `@altinity/clickhouse-http` dependency;
`build/check-boundaries.mjs` gains four new architecture rules (package
source may not import root `src/**`; root may not deep-import package
internals; package source has zero bare import specifiers — closing a gap
the existing bare-specifier-skipping rule loop would otherwise leave open
for any hoisted root dependency; the bare `@altinity/clickhouse-http`
specifier itself is permitted only under `src/net/**`), each mirrored as a
coverage-gated unit test with a non-vacuous sabotage probe.
`Dockerfile` now copies `packages/` before `npm ci`; CI's `unit`/`build`/
`bundle`/`e2e`/`docker` path classifications include `packages/**`. The
existing exact-literal `chUrl()` test suite moved (not duplicated) into the
new package test; the older duplicate in `ch-client.test.ts` is removed.
Bundle-size attribution now classifies `packages/**` as first-party project
code rather than `other`. Eight raw-ESM e2e fixtures that reach the
transport gained an `@altinity/clickhouse-http` import-map entry. This is
phase 2 of 8 (issue #630); consuming query APIs, SQL quoting/type grammar,
auth composition, and final migration/deletion remain later phases.

- **#630 Phase 1: characterize native Fetch/Response/cancellation semantics
ahead of the `@altinity/clickhouse-http` extraction.** No production
behavior changed — `src/net/clickhouse-http-transport.ts`,
Expand Down
21 changes: 18 additions & 3 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,17 @@ all bundled — see hard rule 4). Quality is held by tests.
layout, and application code goes in `src/dashboard/`, with dependency
direction `model/layouts <- application <- UI`. App-level coordination and
sessions go in `src/application/` and must not import `src/ui/` or
`src/editor/`. Network goes in `src/net/` with the fetch seam *injected*,
never imported. DOM rendering goes in `src/ui/` as functions that take the
`src/editor/`. SQL Browser's network *integration and application
policy* (OAuth, `ChCtx`, auth/epoch/retry, product operations) goes in
`src/net/`, with the fetch seam *injected*, never imported. Reusable,
product-agnostic ClickHouse HTTP/Fetch mechanics (URL serialization, the
low-level request) may live in the first-party workspace package
`packages/clickhouse-http` (#630 Phase 2) instead — `src/net/**` is the
only place allowed to import it, by its exact public package name, never
a deep import into its `src/**`; the package itself may depend on
nothing under SQL Browser `src/**` and declares zero runtime
dependencies (mechanically enforced, `build/check-boundaries.mjs`).
DOM rendering goes in `src/ui/` as functions that take the
`app` controller — except the editor, which lives in `src/editor/` behind the
injected editor seams (#143/#212): only `main.js` imports concrete adapters,
and everything else addresses `app.sqlEditor` or `app.specEditor` explicitly.
Expand Down Expand Up @@ -65,7 +74,12 @@ all bundled — see hard rule 4). Quality is held by tests.
fail-closed policy: images/raw HTML/rejected links render as literal
text; measured +44 KB raw / ~3% artifact delta) — all inlined into the
artifact, so the page loads no runtime libraries from third-party CDNs.
Adding *another* runtime dependency is a deliberate decision (it grows the
`packages/clickhouse-http` (#630 Phase 2, the repository's first npm
workspace) is **project source, not an eighth bundled runtime
dependency**: it is private, ships no `dependencies`, and esbuild bundles
it exactly like hand-written `src/**` — `build/size-report-lib.mjs`
attributes it to the `project` ownership bucket, not `external`. Adding
*another* runtime dependency is a deliberate decision (it grows the
single served file) — don't do it casually. When a feature needs a library,
keep the testable logic pure in `src/core/` (chart axis/role/pivot math in
`src/core/chart-data.js`; DOT→positions in `src/core/dot-layout.js`, both
Expand Down Expand Up @@ -120,6 +134,7 @@ Touch these in one change:
|---|---|
| `src/core/*` | pure logic, 100% covered |
| `src/net/*` | OAuth + ClickHouse client, injected fetch |
| `packages/clickhouse-http/src/*` | first-party npm workspace (repo's first, #630 Phase 2) — `chUrl`/URL serialization and the low-level injected-`fetch()` request, behind a public `.` export only; importable from `src/net/**` alone |
| `src/application/*` | app-level coordination, sessions, and pure projections; no UI/editor imports |
| `src/workspace/*` | pure stored-workspace aggregate, persistence contracts, and mutations |
| `src/dashboard/*` | Dashboard model, layouts, and application runtime; dependency direction is mechanically checked |
Expand Down
1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ FROM node:22-bookworm-slim AS build
WORKDIR /app

COPY package.json package-lock.json ./
COPY packages ./packages
COPY build ./build
COPY examples ./examples
COPY schemas ./schemas
Expand Down
66 changes: 66 additions & 0 deletions build/check-boundaries.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,23 @@ const RULES = [
'src/net/oauth-config.ts', 'src/application', 'src/ui'],
why: 'issue #585 Phase 1: the transport contract must not couple to auth/application policy or UI, even type-only',
},
// Issue #630 Phase 2 — Rule A: the new workspace package must not depend on
// ANY SQL Browser source, relatively. (A separate dedicated block below
// additionally bans a browser-root-literal or bare-specifier escape, since
// this generic loop only inspects specifiers starting with '.'.)
{
dir: 'packages/clickhouse-http/src',
forbidden: ['src'],
why: 'issue #630 Phase 2: clickhouse-http must not depend on SQL Browser source',
},
// Issue #630 Phase 2 — Rule C: SQL Browser source must consume the package
// through its public export, never a relative deep import into the
// package's own src/** implementation files.
{
dir: 'src',
forbidden: ['packages/clickhouse-http/src'],
why: 'issue #630 Phase 2: SQL Browser must use the package public export',
},
];

function collectFiles(target) {
Expand Down Expand Up @@ -267,6 +284,55 @@ for (const file of collectFiles(path.join(repoRoot, 'src'))) {
}
}

// Issue #630 Phase 2 — Rule B: packages/clickhouse-http/src/** must have
// ZERO bare specifiers (an empty allowlist, not just an empty manifest
// `dependencies` object). Root hoists many runtime dependencies already
// (e.g. @preact/signals-core), so a zero-dependency manifest alone would not
// stop package source from importing one undeclared — TypeScript/esbuild
// could still resolve it. This block also catches a browser-root-literal
// import (e.g. `/src/net/ch-client.js`) reaching back into SQL Browser
// source: the generic RULES loop above only inspects specifiers that start
// with '.', so a literal absolute-looking path would otherwise slip past
// Rule A undetected — everything that isn't a relative specifier is a
// violation here, with no exceptions.
const PACKAGE_SRC_DIR = path.join(repoRoot, 'packages/clickhouse-http/src');
if (fs.existsSync(PACKAGE_SRC_DIR)) {
for (const file of collectFiles(PACKAGE_SRC_DIR)) {
const relFile = path.relative(repoRoot, file).split(path.sep).join('/');
checkedFiles += 1;
const source = fs.readFileSync(file, 'utf8');
for (const spec of extractSpecifiers(source)) {
if (spec.startsWith('.')) continue; // relative — governed by Rule A above
violations.push(`${relFile} → ${spec} (issue #630 Phase 2: clickhouse-http has zero bare package imports)`);
}
}
}

// Issue #630 Phase 2 — Rule D: the public package name may be imported by
// bare specifier only under src/net/** — the existing network-layer
// boundary — so src/core, src/workspace, src/dashboard, src/application, or
// UI code cannot bypass that layer merely because the low-level HTTP
// mechanics moved behind a bare package name. The deep-import subpath form
// (`@altinity/clickhouse-http/...`) is forbidden EVERYWHERE under src/** —
// only the package's "." export is public (contract A4).
const CLICKHOUSE_HTTP_SPECIFIER = '@altinity/clickhouse-http';
for (const file of collectFiles(path.join(repoRoot, 'src'))) {
const relFile = path.relative(repoRoot, file).split(path.sep).join('/');
checkedFiles += 1;
const source = fs.readFileSync(file, 'utf8');
for (const spec of extractSpecifiers(source)) {
if (spec === CLICKHOUSE_HTTP_SPECIFIER) {
if (!relFile.startsWith('src/net/')) {
violations.push(`${relFile} → ${spec} (issue #630 Phase 2: @altinity/clickhouse-http may only be imported under src/net/**)`);
}
continue;
}
if (spec.startsWith(`${CLICKHOUSE_HTTP_SPECIFIER}/`)) {
violations.push(`${relFile} → ${spec} (issue #630 Phase 2: @altinity/clickhouse-http exposes only its "." export — deep imports are forbidden everywhere)`);
}
}
}

if (violations.length) {
console.error('check-boundaries: architecture violations:');
for (const line of violations) console.error(` ${line}`);
Expand Down
6 changes: 6 additions & 0 deletions build/size-report-lib.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export function normalizeInputPath(p) {
// Attribute one input file to an ownership bucket per the issue's rules:
// src/generated/** -> generated project code
// src/** (everything else) -> hand-written project source
// packages/** -> first-party project source (issue #630
// Phase 2 — the in-repository npm workspace
// is project code, not a third-party
// runtime dependency, even though esbuild
// resolves it through node_modules)
// node_modules/<pkg>/** -> external, grouped under <pkg>
// node_modules/@scope/<pkg>/ -> external, grouped under @scope/<pkg>
// Nested deps (a/node_modules/b) attribute to the *leaf* package (b), which is the
Expand All @@ -42,6 +47,7 @@ export function classifyInput(rawPath) {
}
if (path.startsWith('src/generated/')) return { owner: 'generated', group: 'src/generated' };
if (path.startsWith('src/')) return { owner: 'project', group: 'src' };
if (path.startsWith('packages/')) return { owner: 'project', group: 'packages' };
return { owner: 'other', group: 'other' };
}

Expand Down
34 changes: 33 additions & 1 deletion docs/ADR-0005-clickhouse-web-client.md
Original file line number Diff line number Diff line change
Expand Up @@ -1072,7 +1072,39 @@ review passes patching the same interception design are not expected to
produce a different outcome; this addendum is the record of why the
`/ship` run stopped attempting them.

## Reproduction commands
### #630 Phase 2 extraction addendum (2026-08-07)

**This addendum does not reopen or otherwise touch the Rejected decision
above.** `@clickhouse/client-web` remains rejected for production adoption
for exactly the reasons the "Phase 2 cancellation-incompatibility addendum"
records; nothing here revisits that evidence.

Issue #630 Phase 2 extracts the SQL Browser's OWN hand-rolled Fetch-native
request mechanics — `chUrl`'s URL serialization and the direct injected
`fetch()` invocation, both already proven correct by this ADR's own Phase 0
evidence and by the Phase 1 seam this ADR's "Phase 1 addendum" describes —
out of `src/net/clickhouse-http-transport.ts` and into a new first-party,
in-repository npm workspace package, `packages/clickhouse-http`. This is a
pure internal reorganization of code this ADR already characterized as
"the current custom transport... remains authoritative": no third-party
HTTP client is introduced, no cancellation semantics change (the package's
`request()` still passes the caller's own `AbortSignal` directly into the
real `fetch()` for the response's whole lifetime — exactly the property
whose ABSENCE from `@clickhouse/client-web@1.23.1` is this ADR's own
rejection reason), and `src/net/clickhouse-http-transport.ts` becomes a thin
compatibility adapter delegating to the package rather than building
requests itself. `streamLines()` (the progress-bearing JSON-lines read loop)
stays local to SQL Browser, deferred to a later phase.

The point of the extraction is reuse across a hypothetical future non-bundled
consumer of this exact Fetch-native request boundary — not a step back
toward `@clickhouse/client-web` or any other vendor client, and not itself a
reopening of Phases 2–4 above (those remain gated on a new decision, per the
Rejected verdict). Standalone package build/declaration/npm-pack
stabilization and any external-repository extraction are out of this
phase's scope, tracked separately by the issue's Phase 8.



```sh
# spike-only test suite (does not run under normal `npm test`)
Expand Down
Loading