@hypequery/cli 1.24.1 → 1.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +12 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +4 -1
- package/dist/commands/generate-client.d.ts +7 -0
- package/dist/commands/generate-client.d.ts.map +1 -0
- package/dist/commands/generate-client.js +45 -0
- package/dist/commands/mcp.d.ts +2 -0
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +9 -1
- package/dist/generators/client-contract/input-types.d.ts +7 -0
- package/dist/generators/client-contract/input-types.d.ts.map +1 -0
- package/dist/generators/client-contract/input-types.js +34 -0
- package/dist/generators/client-contract/render.d.ts +3 -0
- package/dist/generators/client-contract/render.d.ts.map +1 -0
- package/dist/generators/client-contract/render.js +46 -0
- package/dist/utils/client-contract-file.d.ts +6 -0
- package/dist/utils/client-contract-file.d.ts.map +1 -0
- package/dist/utils/client-contract-file.js +58 -0
- package/dist/utils/dependency-installer.d.ts.map +1 -1
- package/dist/utils/dependency-installer.js +25 -1
- package/dist/utils/load-env-files.js +1 -1
- package/dist/utils/load-serve-dev.d.ts +6 -0
- package/dist/utils/load-serve-dev.d.ts.map +1 -0
- package/dist/utils/load-serve-dev.js +18 -0
- package/dist/utils/mcp-http.d.ts +12 -0
- package/dist/utils/mcp-http.d.ts.map +1 -0
- package/dist/utils/mcp-http.js +65 -0
- package/dist/utils/mcp-lifecycle.d.ts +1 -1
- package/dist/utils/mcp-lifecycle.d.ts.map +1 -1
- package/dist/utils/mcp-lifecycle.js +10 -2
- package/dist/utils/telemetry/catalog.d.ts +111 -5
- package/dist/utils/telemetry/catalog.d.ts.map +1 -1
- package/dist/utils/telemetry/catalog.js +13 -4
- package/dist/utils/telemetry/domains.d.ts +5 -3
- package/dist/utils/telemetry/domains.d.ts.map +1 -1
- package/dist/utils/telemetry/domains.js +5 -3
- package/dist/utils/telemetry/mcp-session.js +2 -2
- package/package.json +15 -12
package/README.md
CHANGED
|
@@ -37,12 +37,44 @@ npx hypequery init \
|
|
|
37
37
|
| `hypequery dev` | Local API, docs, and hot reload |
|
|
38
38
|
| `hypequery generate` | Fresh TypeScript types from ClickHouse or chDB |
|
|
39
39
|
| `hypequery generate:datasets` | Dataset definitions scaffolded from tables |
|
|
40
|
+
| `hypequery generate:client` | TypeScript API types and routes from a Python client contract |
|
|
40
41
|
| `hypequery generate:manifest` | A browser-safe route manifest for React hooks |
|
|
41
42
|
| `hypequery pull` / `diff` | Live source inspection and comparison |
|
|
42
43
|
| `hypequery deployment:status` | Live release and Cloud-provided connection URLs |
|
|
43
44
|
|
|
44
45
|
Non-interactive ClickHouse commands read `CLICKHOUSE_URL`, `CLICKHOUSE_DATABASE`, `CLICKHOUSE_USERNAME`, and `CLICKHOUSE_PASSWORD`.
|
|
45
46
|
|
|
47
|
+
## Generate a client for Python endpoints
|
|
48
|
+
|
|
49
|
+
Export a build contract from your Python definition-only factory, then generate
|
|
50
|
+
one TypeScript file with no backend imports:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
# In the Python project (the factory returns export_client_contract(...)):
|
|
54
|
+
hypequery export client-contract --factory analytics.contract:build --output client-contract.json
|
|
55
|
+
|
|
56
|
+
# In the frontend project:
|
|
57
|
+
npx hypequery generate:client ../backend/client-contract.json --output src/hypequery-client.ts
|
|
58
|
+
npx hypequery generate:client ../backend/client-contract.json --output src/hypequery-client.ts --check
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
The file exports `ClientApi`, `clientManifest`, `clientContractHash` and public
|
|
62
|
+
response metadata types. It preserves installed POST paths, endpoint schema
|
|
63
|
+
hashes, metric aliases, default measures, field-specific filter types and wire
|
|
64
|
+
nullability. Numeric dimensions can be strings on the wire; aggregate values are
|
|
65
|
+
strings or null. Time buckets may be null when the physical time column is null.
|
|
66
|
+
Results use optional fields until a consumer narrows the query's selections.
|
|
67
|
+
|
|
68
|
+
Generation reads local JSON only (1 MiB maximum); it never evaluates a backend
|
|
69
|
+
module or fetches an endpoint. Signed identities, duplicate JSON members and
|
|
70
|
+
strict contract bounds are checked before writing. Use the owner-controlled
|
|
71
|
+
`build` view; principal-specific live discovery is for runtime consumers.
|
|
72
|
+
`--check` detects drift without creating directories or changing files. Existing
|
|
73
|
+
output requires `--force` to replace, and symlinked output paths are refused.
|
|
74
|
+
Generated output is bounded to 16 MiB. TypeScript describes structural inputs;
|
|
75
|
+
the Python endpoint still validates timezones, selection dependencies, query
|
|
76
|
+
budgets and authorization on every request.
|
|
77
|
+
|
|
46
78
|
## Why use the CLI
|
|
47
79
|
|
|
48
80
|
- Get correct ClickHouse runtime types instead of guessing interfaces.
|
|
@@ -58,3 +90,36 @@ Non-interactive ClickHouse commands read `CLICKHOUSE_URL`, `CLICKHOUSE_DATABASE`
|
|
|
58
90
|
## License
|
|
59
91
|
|
|
60
92
|
Apache-2.0.
|
|
93
|
+
|
|
94
|
+
### Python HTTP datasets over MCP
|
|
95
|
+
|
|
96
|
+
For a Python API exposing an authorized client-contract endpoint, install a
|
|
97
|
+
compatible `@hypequery/mcp` alongside the CLI in your Node project, then run:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# Supply the backend principal's Bearer credential through your environment.
|
|
101
|
+
# Omit it only when your backend intentionally authorizes public access.
|
|
102
|
+
export HYPEQUERY_API_KEY='your-backend-credential'
|
|
103
|
+
hypequery mcp --client-contract-url https://api.example.com/v1/client-contract --self-test
|
|
104
|
+
hypequery mcp --client-contract-url https://api.example.com/v1/client-contract
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
The self-test checks authorized discovery and tool schemas without querying
|
|
108
|
+
business data. Serving uses MCP stdio: stdout carries protocol messages and
|
|
109
|
+
status/errors go to stderr. Use the second command as your MCP client's command
|
|
110
|
+
and arguments; supply the credential using that client's secret/environment
|
|
111
|
+
configuration. Never put the credential in a URL or command argument.
|
|
112
|
+
|
|
113
|
+
Python owns authentication, tenant resolution, policy and SQL execution. One
|
|
114
|
+
bridge process serves one backend principal. Restart it after changing principal
|
|
115
|
+
or after a stale-contract error; it never silently replaces pinned schemas or
|
|
116
|
+
replays a query. HTTPS is required except on loopback, and redirects are refused.
|
|
117
|
+
|
|
118
|
+
`--client-contract-url` cannot be combined with a local file, `--path`, `--tenant`
|
|
119
|
+
or `--url`. The existing `--self-test --url` checks a hosted MCP endpoint; the
|
|
120
|
+
new flag points at a Python HTTP discovery endpoint. No local analytics module
|
|
121
|
+
or Python process is loaded by this mode. Node is needed for the MCP bridge;
|
|
122
|
+
the Python API itself does not require Node.
|
|
123
|
+
|
|
124
|
+
The npm packages must contain the matching client-contract features. Workspace
|
|
125
|
+
qualification does not imply that these changes have already been published.
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AA0CpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAE9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AAySD,OAAO,EAAE,OAAO,EAAE,CAAC"}
|
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,7 @@ import { mcpCommand } from './commands/mcp.js';
|
|
|
4
4
|
import { devCommand } from './commands/dev.js';
|
|
5
5
|
import { generateCommand } from './commands/generate.js';
|
|
6
6
|
import { generateDatasetsCommand } from './commands/generate-datasets.js';
|
|
7
|
+
import { generateClientCommand } from './commands/generate-client.js';
|
|
7
8
|
import { generateManifestCommand } from './commands/generate-manifest.js';
|
|
8
9
|
import { buildDeploymentCommand, prepareDeploymentReleaseCommand, validateDeploymentCommand, } from './commands/deployment.js';
|
|
9
10
|
import { deployCommand, submitDeploymentCommand, } from './commands/deploy.js';
|
|
@@ -132,11 +133,12 @@ program
|
|
|
132
133
|
}));
|
|
133
134
|
program
|
|
134
135
|
.command('mcp [file]')
|
|
135
|
-
.description('Serve
|
|
136
|
+
.description('Serve local datasets or authorized HTTP client endpoints over MCP stdio')
|
|
136
137
|
.option('--path <path>', 'Analytics directory (loads <path>/api.ts or <path>/queries.ts)')
|
|
137
138
|
.option('--tenant <id>', 'Trusted tenant applied to every tenant-scoped dataset')
|
|
138
139
|
.option('--self-test', 'Check the entrypoint and exit without speaking MCP')
|
|
139
140
|
.option('--url <url>', 'With --self-test: check a hosted MCP endpoint (key from HYPEQUERY_API_KEY)')
|
|
141
|
+
.option('--client-contract-url <url>', 'Bridge authorized HTTP client endpoints to MCP (Bearer key from HYPEQUERY_API_KEY)')
|
|
140
142
|
.action(runCommand(async (file, options) => {
|
|
141
143
|
await mcpCommand(file, options);
|
|
142
144
|
}));
|
|
@@ -163,6 +165,15 @@ program
|
|
|
163
165
|
.action(runCommand(async (options) => {
|
|
164
166
|
await generateDatasetsCommand(options);
|
|
165
167
|
}));
|
|
168
|
+
program
|
|
169
|
+
.command('generate:client <contract>')
|
|
170
|
+
.description('Generate browser-safe TypeScript API types and routes from a client contract JSON file')
|
|
171
|
+
.option('-o, --output <path>', 'Output TypeScript file (default: analytics/hypequery-client.ts)')
|
|
172
|
+
.option('--check', 'Check for drift without writing')
|
|
173
|
+
.option('--force', 'Replace an existing output file')
|
|
174
|
+
.action(runCommand(async (contract, options) => {
|
|
175
|
+
await generateClientCommand(contract, options);
|
|
176
|
+
}));
|
|
166
177
|
program
|
|
167
178
|
.command('generate:manifest <api>')
|
|
168
179
|
.description('Generate a static React hook manifest from a hypequery API module')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AAmCA,MAAM,WAAW,UAAU;IACzB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE,UAAe,iBA8OvE"}
|
package/dist/commands/dev.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { loadServeDev } from '../utils/load-serve-dev.js';
|
|
1
2
|
import { exitWith, finishAndExit } from '../utils/command-exit.js';
|
|
2
3
|
import { telemetryErrorCode } from '../utils/telemetry/error-code.js';
|
|
3
4
|
import { DevSession } from '../utils/telemetry/dev-session.js';
|
|
@@ -69,6 +70,9 @@ export async function devCommand(file, options = {}) {
|
|
|
69
70
|
const dbSpinner = ora('Connecting to ClickHouse...').start();
|
|
70
71
|
let loading = true;
|
|
71
72
|
try {
|
|
73
|
+
// Before the API module: it imports @hypequery/serve itself, and failing
|
|
74
|
+
// there would hide which package to install.
|
|
75
|
+
const { serveDev } = await loadServeDev();
|
|
72
76
|
// Load the API module
|
|
73
77
|
const api = await loadApiModule(queriesFile);
|
|
74
78
|
loading = false;
|
|
@@ -92,7 +96,6 @@ export async function devCommand(file, options = {}) {
|
|
|
92
96
|
logger.success(`Registered ${queryCount} ${queryCount === 1 ? 'query' : 'queries'}`);
|
|
93
97
|
logger.newline();
|
|
94
98
|
// Start the server
|
|
95
|
-
const { serveDev } = await import('@hypequery/serve/dev');
|
|
96
99
|
currentServer = await serveDev(api, {
|
|
97
100
|
port: options.port,
|
|
98
101
|
hostname: options.hostname,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-client.d.ts","sourceRoot":"","sources":["../../src/commands/generate-client.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,qBAAqB;IAAG,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE;AAE5F,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,GAAE,qBAA0B,GAAG,OAAO,CAAC,IAAI,CAAC,CA8BhH"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { decodeProtocolClientContract } from '@hypequery/protocol';
|
|
3
|
+
import { renderClientContract } from '../generators/client-contract/render.js';
|
|
4
|
+
import { checkClientOutputPath, readClientArtifact, readClientOutput } from '../utils/client-contract-file.js';
|
|
5
|
+
import { writeGeneratedFileAtomically } from '../utils/generated-file.js';
|
|
6
|
+
import { logger } from '../utils/logger.js';
|
|
7
|
+
import { updateCommandTelemetry, setCommandTelemetryError } from '../utils/telemetry/command-context.js';
|
|
8
|
+
import { countBucket } from '../utils/telemetry/buckets.js';
|
|
9
|
+
export async function generateClientCommand(artifact, options = {}) {
|
|
10
|
+
updateCommandTelemetry('generate:client', { mode: options.check ? 'check' : 'write', force: !!options.force, custom_output: options.output !== undefined });
|
|
11
|
+
if (options.force && options.check)
|
|
12
|
+
throw new Error('--force cannot be combined with --check.');
|
|
13
|
+
const input = path.resolve(artifact);
|
|
14
|
+
const output = path.resolve(options.output ?? 'analytics/hypequery-client.ts');
|
|
15
|
+
if (input === output)
|
|
16
|
+
throw new Error('Client input and output must be different files.');
|
|
17
|
+
await checkClientOutputPath(output);
|
|
18
|
+
const current = await readClientOutput(output);
|
|
19
|
+
const contract = decodeProtocolClientContract(await readClientArtifact(input));
|
|
20
|
+
if (contract.view !== 'build')
|
|
21
|
+
throw new Error('Client generation requires a build view exported by the definition owner.');
|
|
22
|
+
const contents = renderClientContract(contract);
|
|
23
|
+
if (Buffer.byteLength(contents) > 16 * 1_048_576)
|
|
24
|
+
throw new Error('Generated client exceeds the 16 MiB output limit.');
|
|
25
|
+
updateCommandTelemetry('generate:client', { endpoint_count_bucket: countBucket(contract.endpoints.length) });
|
|
26
|
+
if (current === contents) {
|
|
27
|
+
updateCommandTelemetry('generate:client', { check_result: 'up_to_date' });
|
|
28
|
+
logger.success('Client types and routes are up to date.');
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
if (options.check) {
|
|
32
|
+
updateCommandTelemetry('generate:client', { check_result: current === undefined ? 'missing' : 'out_of_date' });
|
|
33
|
+
setCommandTelemetryError(current === undefined ? 'output_missing' : 'output_out_of_date');
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
logger.error('Client types and routes are missing or out of date.');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (current !== undefined && !options.force)
|
|
39
|
+
throw new Error('Refusing to overwrite client types; use --check or --force.');
|
|
40
|
+
await checkClientOutputPath(output);
|
|
41
|
+
if (await readClientOutput(output) !== current)
|
|
42
|
+
throw new Error('Client output changed during generation; retry after reviewing it.');
|
|
43
|
+
await writeGeneratedFileAtomically(output, contents, { overwrite: options.force === true });
|
|
44
|
+
logger.success('Generated client types and routes.');
|
|
45
|
+
}
|
package/dist/commands/mcp.d.ts
CHANGED
|
@@ -9,6 +9,8 @@ export interface McpOptions {
|
|
|
9
9
|
selfTest?: boolean;
|
|
10
10
|
/** With `selfTest`: check this hosted MCP endpoint instead of a local entrypoint. */
|
|
11
11
|
url?: string;
|
|
12
|
+
/** Authorized HTTP client-contract discovery; no local analytics entrypoint. */
|
|
13
|
+
clientContractUrl?: string;
|
|
12
14
|
}
|
|
13
15
|
export interface McpDependencies {
|
|
14
16
|
loadApi?: (file: string) => Promise<unknown>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../../src/commands/mcp.ts"],"names":[],"mappings":"AAEA,OAAO,EAAqB,KAAK,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AASvF,OAAO,EAAkB,KAAK,cAAc,EAAE,MAAM,8BAA8B,CAAC;AAMnF,MAAM,WAAW,UAAU;IACzB,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,6DAA6D;IAC7D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,wEAAwE;IACxE,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,qFAAqF;IACrF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC5B;AAED,MAAM,WAAW,eAAe;IAC9B,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7C,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE;QACf,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;QAClC,SAAS,EAAE,OAAO,CAAC;QACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;KACnB,KAAK,OAAO,CAAC,kBAAkB,CAAC,CAAC;IAClC,mEAAmE;IACnE,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACjC,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,GAAG,EAAE,MAAM,CAAA;KAAE,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAC/E,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;CACpD;AAoED,wBAAsB,UAAU,CAC9B,IAAI,CAAC,EAAE,MAAM,EACb,OAAO,GAAE,UAAe,EACxB,YAAY,GAAE,eAAoB,GACjC,OAAO,CAAC,IAAI,CAAC,CAiFf"}
|
package/dist/commands/mcp.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { runHttpMcp } from '../utils/mcp-http.js';
|
|
1
2
|
import { exitWith } from '../utils/command-exit.js';
|
|
2
3
|
import { runMcpUntilSignal } from '../utils/mcp-lifecycle.js';
|
|
3
4
|
import { routeConsoleOutputToStderr } from '../utils/mcp-console.js';
|
|
@@ -77,7 +78,10 @@ function resolveSource(api, file) {
|
|
|
77
78
|
}
|
|
78
79
|
export async function mcpCommand(file, options = {}, dependencies = {}) {
|
|
79
80
|
const telemetry = new McpSession();
|
|
80
|
-
updateCommandTelemetry('mcp', { mode: options.selfTest ? 'self_test' : 'serve', hosted: !!options.url, tenant_used: !!options.tenant });
|
|
81
|
+
updateCommandTelemetry('mcp', { mode: options.selfTest ? 'self_test' : 'serve', hosted: !!options.url, tenant_used: !!options.tenant, http_backend: !!options.clientContractUrl });
|
|
82
|
+
if (options.clientContractUrl !== undefined && (file !== undefined || options.path !== undefined || options.tenant !== undefined || options.url !== undefined)) {
|
|
83
|
+
fail('--client-contract-url cannot be combined with a file, --path, --tenant or --url.');
|
|
84
|
+
}
|
|
81
85
|
if (options.url && !options.selfTest) {
|
|
82
86
|
fail('--url is only used with --self-test.', [
|
|
83
87
|
'MCP clients connect to the hosted endpoint directly; this command serves a local project.',
|
|
@@ -85,6 +89,10 @@ export async function mcpCommand(file, options = {}, dependencies = {}) {
|
|
|
85
89
|
}
|
|
86
90
|
const restoreConsole = routeConsoleOutputToStderr();
|
|
87
91
|
try {
|
|
92
|
+
if (options.clientContractUrl !== undefined) {
|
|
93
|
+
await runHttpMcp(options.clientContractUrl, !!options.selfTest, dependencies, telemetry);
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
88
96
|
if (options.url) {
|
|
89
97
|
await remoteSelfTest(options.url, dependencies);
|
|
90
98
|
return;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ProtocolClientEndpoint } from '@hypequery/protocol';
|
|
2
|
+
export declare const literal: (value: string) => string;
|
|
3
|
+
export declare const union: (values: readonly string[]) => string;
|
|
4
|
+
export declare const names: (values: readonly string[]) => string;
|
|
5
|
+
/** Structural types mirror the closed profile; runtime also checks selection dependencies and budgets. */
|
|
6
|
+
export declare function clientInputType(endpoint: ProtocolClientEndpoint): string;
|
|
7
|
+
//# sourceMappingURL=input-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input-types.d.ts","sourceRoot":"","sources":["../../../src/generators/client-contract/input-types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAgB,sBAAsB,EAAE,MAAM,qBAAqB,CAAC;AAEhF,eAAO,MAAM,OAAO,GAAI,OAAO,MAAM,KAAG,MAA+B,CAAC;AACxE,eAAO,MAAM,KAAK,GAAI,QAAQ,SAAS,MAAM,EAAE,KAAG,MAAsD,CAAC;AACzG,eAAO,MAAM,KAAK,GAAI,QAAQ,SAAS,MAAM,EAAE,KAAG,MAAgD,CAAC;AAWnG,0GAA0G;AAC1G,wBAAgB,eAAe,CAAC,QAAQ,EAAE,sBAAsB,GAAG,MAAM,CAqBxE"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export const literal = (value) => JSON.stringify(value);
|
|
2
|
+
export const union = (values) => values.length ? values.join(' | ') : 'never';
|
|
3
|
+
export const names = (values) => union([...values].sort().map(literal));
|
|
4
|
+
function predicate(field, definition, having = false) {
|
|
5
|
+
const scalar = definition.type === 'number' ? 'number' : definition.type === 'boolean' ? 'boolean' : 'string';
|
|
6
|
+
return [...definition.operators].sort().map(operator => {
|
|
7
|
+
const value = operator === 'between' ? `readonly [${scalar}, ${scalar}]`
|
|
8
|
+
: operator === 'in' || operator === 'notIn' ? `readonly [${scalar}, ...${scalar}[]]` : scalar;
|
|
9
|
+
return `{ readonly ${having ? 'measure' : 'field'}: ${literal(field)}; readonly operator: ${literal(operator)}; readonly value: ${value} }`;
|
|
10
|
+
});
|
|
11
|
+
}
|
|
12
|
+
/** Structural types mirror the closed profile; runtime also checks selection dependencies and budgets. */
|
|
13
|
+
export function clientInputType(endpoint) {
|
|
14
|
+
const dimensions = Object.keys(endpoint.dimensions).filter(key => endpoint.dimensions[key].groupable);
|
|
15
|
+
const measures = Object.keys(endpoint.measures);
|
|
16
|
+
const filters = Object.keys(endpoint.filters).sort().flatMap(key => predicate(key, endpoint.filters[key]));
|
|
17
|
+
const having = measures.filter(key => endpoint.measures[key].having).sort().flatMap(key => predicate(key, {
|
|
18
|
+
type: 'number', operators: ['eq', 'neq', 'gt', 'gte', 'lt', 'lte', 'between', 'in', 'notIn'],
|
|
19
|
+
}, true));
|
|
20
|
+
const ordered = [...dimensions.filter(key => endpoint.dimensions[key].orderable), ...measures,
|
|
21
|
+
...(endpoint.time ? ['period'] : [])];
|
|
22
|
+
return `{
|
|
23
|
+
readonly dimensions?: readonly (${names(dimensions)})[];
|
|
24
|
+
readonly filters?: readonly (${union(filters)})[];
|
|
25
|
+
readonly orderBy?: readonly { readonly field: ${names(ordered)}; readonly direction: 'asc' | 'desc' }[];
|
|
26
|
+
readonly limit?: number;
|
|
27
|
+
readonly offset?: number;
|
|
28
|
+
readonly includeMeta?: ${endpoint.metadata ? 'boolean' : 'false'};
|
|
29
|
+
${endpoint.kind === 'dataset' ? `readonly measures?: readonly (${names(measures)})[];
|
|
30
|
+
readonly having?: readonly (${union(having)})[];` : ''}
|
|
31
|
+
${endpoint.time ? `readonly by?: ${names(endpoint.time.grains)};
|
|
32
|
+
${endpoint.time.timezone ? 'readonly timezone?: string;' : ''}` : ''}
|
|
33
|
+
}`;
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/generators/client-contract/render.ts"],"names":[],"mappings":"AA0BA,kGAAkG;AAClG,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAqB5D"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { validateProtocolClientContract } from '@hypequery/protocol';
|
|
2
|
+
import { clientInputType, literal } from './input-types.js';
|
|
3
|
+
function dimensionType(definition) {
|
|
4
|
+
const wire = definition.wireType === 'number-or-string' ? 'number | string' : definition.wireType;
|
|
5
|
+
return wire + (definition.nullable === false ? '' : ' | null');
|
|
6
|
+
}
|
|
7
|
+
function endpointType(endpoint) {
|
|
8
|
+
const dimensions = Object.keys(endpoint.dimensions).sort().filter(key => endpoint.dimensions[key].groupable)
|
|
9
|
+
.map(key => `readonly ${literal(key)}?: ${dimensionType(endpoint.dimensions[key])};`);
|
|
10
|
+
const measures = Object.keys(endpoint.measures).sort().map(key => `readonly ${literal(key)}?: string | null;`);
|
|
11
|
+
// The physical time column may be nullable; the descriptor makes no non-null guarantee.
|
|
12
|
+
const row = `{ ${[...dimensions, ...measures, ...(endpoint.time ? ['readonly period?: string | null;'] : [])].join('\n')} }`;
|
|
13
|
+
const semantic = {
|
|
14
|
+
version: 1, kind: endpoint.kind, dimensions: Object.fromEntries(Object.keys(endpoint.dimensions).sort().map(key => [key, { wireType: endpoint.dimensions[key].wireType, nullable: endpoint.dimensions[key].nullable }])),
|
|
15
|
+
...(endpoint.kind === 'dataset' ? { measures: Object.fromEntries(Object.keys(endpoint.measures).sort().map(key => [key, { wireType: 'string', nullable: true }])), defaultMeasures: endpoint.defaultMeasures }
|
|
16
|
+
: { metricName: Object.keys(endpoint.measures)[0] }),
|
|
17
|
+
};
|
|
18
|
+
return `{
|
|
19
|
+
input: ${clientInputType(endpoint)};
|
|
20
|
+
output: { data: ${row}[]; meta?: ClientResponseMeta | null };
|
|
21
|
+
readonly __hypequeryClientSemantic?: ${JSON.stringify(semantic)};
|
|
22
|
+
}`;
|
|
23
|
+
}
|
|
24
|
+
/** Data-only generator: no API module evaluation, credentials, drivers or server dependencies. */
|
|
25
|
+
export function renderClientContract(source) {
|
|
26
|
+
const contract = validateProtocolClientContract(source);
|
|
27
|
+
const endpoints = [...contract.endpoints].sort((a, b) => a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
|
|
28
|
+
const entries = endpoints.map(endpoint => ({ endpoint, key: endpoint.kind === 'dataset' ? `dataset:${endpoint.id}` : endpoint.id }));
|
|
29
|
+
return `// Generated by hypequery generate:client. Do not edit.
|
|
30
|
+
// Runtime validation still enforces selection dependencies, timezones and query budgets.
|
|
31
|
+
export interface ClientResponseMeta {
|
|
32
|
+
requestId: string;
|
|
33
|
+
timingMs: number;
|
|
34
|
+
rowCount: number;
|
|
35
|
+
pagination: { limit: number; offset: number; hasMore: boolean };
|
|
36
|
+
cache?: { hit: boolean };
|
|
37
|
+
}
|
|
38
|
+
export type ClientApi = {
|
|
39
|
+
${entries.map(({ endpoint, key }) => ` ${literal(key)}: ${endpointType(endpoint)};`).join('\n')}
|
|
40
|
+
};
|
|
41
|
+
export const clientManifest = {
|
|
42
|
+
${entries.map(({ endpoint, key }) => ` [${literal(key)}]: { method: 'POST', path: ${literal(endpoint.path)}, schemaHash: ${literal(endpoint.schemaHash)} },`).join('\n')}
|
|
43
|
+
} as const;
|
|
44
|
+
export const clientContractHash = ${literal(contract.contentHash)} as const;
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Refuse filesystem indirection for generated output and its existing ancestors. */
|
|
2
|
+
export declare function checkClientOutputPath(filePath: string): Promise<void>;
|
|
3
|
+
/** Bound allocation before reading; O_NOFOLLOW also refuses a swapped final symlink. */
|
|
4
|
+
export declare function readClientArtifact(filePath: string, maxBytes?: number): Promise<string>;
|
|
5
|
+
export declare function readClientOutput(filePath: string): Promise<string | undefined>;
|
|
6
|
+
//# sourceMappingURL=client-contract-file.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-contract-file.d.ts","sourceRoot":"","sources":["../../src/utils/client-contract-file.ts"],"names":[],"mappings":"AAIA,qFAAqF;AACrF,wBAAsB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAe3E;AAED,wFAAwF;AACxF,wBAAsB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,QAAQ,SAAY,GAAG,OAAO,CAAC,MAAM,CAAC,CAkBhG;AAED,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAMpF"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { constants } from 'node:fs';
|
|
2
|
+
import { lstat, open } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
/** Refuse filesystem indirection for generated output and its existing ancestors. */
|
|
5
|
+
export async function checkClientOutputPath(filePath) {
|
|
6
|
+
let current = path.resolve(filePath);
|
|
7
|
+
while (true) {
|
|
8
|
+
try {
|
|
9
|
+
const info = await lstat(current);
|
|
10
|
+
if (info.isSymbolicLink() || (current === path.resolve(filePath) ? !info.isFile() : !info.isDirectory())) {
|
|
11
|
+
throw new Error('Client output must be a regular file under ordinary directories.');
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (error.code !== 'ENOENT')
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
const parent = path.dirname(current);
|
|
19
|
+
if (parent === current)
|
|
20
|
+
return;
|
|
21
|
+
current = parent;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/** Bound allocation before reading; O_NOFOLLOW also refuses a swapped final symlink. */
|
|
25
|
+
export async function readClientArtifact(filePath, maxBytes = 1_048_576) {
|
|
26
|
+
const handle = await open(filePath, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
|
|
27
|
+
try {
|
|
28
|
+
const before = await handle.stat();
|
|
29
|
+
if (!before.isFile() || before.size > maxBytes)
|
|
30
|
+
throw new Error('Client artifact must be a regular file within the byte limit.');
|
|
31
|
+
const buffer = Buffer.alloc(maxBytes + 1);
|
|
32
|
+
let size = 0;
|
|
33
|
+
while (size < buffer.length) {
|
|
34
|
+
const { bytesRead } = await handle.read(buffer, size, buffer.length - size, null);
|
|
35
|
+
if (!bytesRead)
|
|
36
|
+
break;
|
|
37
|
+
size += bytesRead;
|
|
38
|
+
}
|
|
39
|
+
const after = await handle.stat();
|
|
40
|
+
if (size > maxBytes || before.size !== after.size || before.mtimeMs !== after.mtimeMs || before.ctimeMs !== after.ctimeMs) {
|
|
41
|
+
throw new Error('Client artifact exceeded its byte limit or changed while reading.');
|
|
42
|
+
}
|
|
43
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(buffer.subarray(0, size));
|
|
44
|
+
}
|
|
45
|
+
finally {
|
|
46
|
+
await handle.close();
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export async function readClientOutput(filePath) {
|
|
50
|
+
try {
|
|
51
|
+
return await readClientArtifact(filePath, 16 * 1_048_576);
|
|
52
|
+
}
|
|
53
|
+
catch (error) {
|
|
54
|
+
if (error.code === 'ENOENT')
|
|
55
|
+
return undefined;
|
|
56
|
+
throw error;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"dependency-installer.d.ts","sourceRoot":"","sources":["../../src/utils/dependency-installer.ts"],"names":[],"mappings":"AAWA,KAAK,aAAa,GAAG,SAAS,GAAG,UAAU,CAAC;AAC5C,KAAK,gBAAgB,GAAG,YAAY,GAAG,MAAM,CAAC;AAwE9C,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEpE;AAqCD,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,KAAK,GAAE,aAAyB,EAChC,QAAQ,GAAE,gBAA+B,GACxC,MAAM,EAAE,CAmBV;
|
|
1
|
+
{"version":3,"file":"dependency-installer.d.ts","sourceRoot":"","sources":["../../src/utils/dependency-installer.ts"],"names":[],"mappings":"AAWA,KAAK,aAAa,GAAG,SAAS,GAAG,UAAU,CAAC;AAC5C,KAAK,gBAAgB,GAAG,YAAY,GAAG,MAAM,CAAC;AAwE9C,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAEpE;AAqCD,wBAAgB,uBAAuB,CACrC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,KAAK,GAAE,aAAyB,EAChC,QAAQ,GAAE,gBAA+B,GACxC,MAAM,EAAE,CAmBV;AA8CD,wBAAsB,2BAA2B,CAC/C,KAAK,GAAE,aAAyB,EAChC,QAAQ,GAAE,gBAA+B,iBAyD1C;AAED,eAAO,MAAM,wBAAwB,oCAA8B,CAAC"}
|
|
@@ -4,7 +4,7 @@ import path from 'node:path';
|
|
|
4
4
|
import { spawn } from 'node:child_process';
|
|
5
5
|
import { fileURLToPath } from 'node:url';
|
|
6
6
|
import { logger } from './logger.js';
|
|
7
|
-
const ZOD_SCAFFOLD_VERSION = '^
|
|
7
|
+
const ZOD_SCAFFOLD_VERSION = '^4.6.6';
|
|
8
8
|
// chdb/hypequery (the embedded adapter subpath) ships from chdb 3.2.0.
|
|
9
9
|
const CHDB_SCAFFOLD_VERSION = '^3.2.0';
|
|
10
10
|
const STABLE_SCAFFOLD_PACKAGES = ['@hypequery/clickhouse', '@hypequery/serve', `zod@${ZOD_SCAFFOLD_VERSION}`];
|
|
@@ -120,12 +120,36 @@ export function resolveScaffoldPackages(cliVersion, style = 'queries', database
|
|
|
120
120
|
...(includeChdb ? [`chdb@${CHDB_SCAFFOLD_VERSION}`] : []),
|
|
121
121
|
];
|
|
122
122
|
}
|
|
123
|
+
/** The oldest Zod 3 that Serve's `zod` peer range (`^3.23.8 || ^4.0.0`) accepts. */
|
|
124
|
+
const SERVE_MIN_ZOD_3 = [3, 23, 8];
|
|
125
|
+
/**
|
|
126
|
+
* Whether a project's `zod` range can only resolve versions Serve accepts:
|
|
127
|
+
* its lowest version, the first `x.y.z` in it (missing parts count as 0), is
|
|
128
|
+
* Zod 4 or at least 3.23.8. So `^3.23.8`, `~4.1.0` and `4` are kept, while
|
|
129
|
+
* `3.1.0`, `~3.1.0` and `3` are upgraded. Tags such as `latest` are upgraded.
|
|
130
|
+
*/
|
|
131
|
+
function isServeCompatibleZodRange(range) {
|
|
132
|
+
const match = /(\d+)(?:\.(\d+))?(?:\.(\d+))?/.exec(range);
|
|
133
|
+
if (!match)
|
|
134
|
+
return false;
|
|
135
|
+
const [major, minor, patch] = [match[1], match[2], match[3]].map(part => Number(part ?? 0));
|
|
136
|
+
if (major === 4)
|
|
137
|
+
return true;
|
|
138
|
+
if (major !== 3)
|
|
139
|
+
return false;
|
|
140
|
+
const [, minMinor, minPatch] = SERVE_MIN_ZOD_3;
|
|
141
|
+
return minor > minMinor || (minor === minMinor && patch >= minPatch);
|
|
142
|
+
}
|
|
123
143
|
function shouldInstallPackage(pkgJson, specifier) {
|
|
124
144
|
const name = packageNameFromSpecifier(specifier);
|
|
125
145
|
const existingVersion = getDependencyVersion(pkgJson, name);
|
|
126
146
|
if (!existingVersion) {
|
|
127
147
|
return true;
|
|
128
148
|
}
|
|
149
|
+
// Serve accepts Zod 3 or Zod 4, so keep whichever the project already uses.
|
|
150
|
+
if (name === 'zod' && isServeCompatibleZodRange(existingVersion)) {
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
129
153
|
const desiredHasPinnedVersion = specifier.startsWith('@')
|
|
130
154
|
? specifier.indexOf('@', specifier.indexOf('/') + 1) !== -1
|
|
131
155
|
: specifier.includes('@');
|
|
@@ -18,7 +18,7 @@ export async function loadEnvFiles(directory) {
|
|
|
18
18
|
const { config } = await import('dotenv');
|
|
19
19
|
for (const file of envFiles(process.env.NODE_ENV)) {
|
|
20
20
|
// Missing files are a no-op.
|
|
21
|
-
config({ path: path.join(resolved, file) });
|
|
21
|
+
config({ path: path.join(resolved, file), quiet: true });
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
catch {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@hypequery/serve` is an optional peer: projects that only generate clients
|
|
3
|
+
* (a React app, for example) should not have server packages installed for them.
|
|
4
|
+
*/
|
|
5
|
+
export declare function loadServeDev(load?: () => Promise<typeof import('@hypequery/serve/dev')>): Promise<typeof import('@hypequery/serve/dev')>;
|
|
6
|
+
//# sourceMappingURL=load-serve-dev.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-serve-dev.d.ts","sourceRoot":"","sources":["../../src/utils/load-serve-dev.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,wBAAsB,YAAY,CAChC,IAAI,GAAE,MAAM,OAAO,CAAC,cAAc,sBAAsB,CAAC,CAAwC,GAChG,OAAO,CAAC,cAAc,sBAAsB,CAAC,CAAC,CAYhD"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@hypequery/serve` is an optional peer: projects that only generate clients
|
|
3
|
+
* (a React app, for example) should not have server packages installed for them.
|
|
4
|
+
*/
|
|
5
|
+
export async function loadServeDev(load = () => import('@hypequery/serve/dev')) {
|
|
6
|
+
try {
|
|
7
|
+
return await load();
|
|
8
|
+
}
|
|
9
|
+
catch (error) {
|
|
10
|
+
const code = error.code;
|
|
11
|
+
const message = error instanceof Error ? error.message : '';
|
|
12
|
+
// Only serve itself: a module serve fails to find is a different problem.
|
|
13
|
+
if ((code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND') && message.includes("'@hypequery/serve")) {
|
|
14
|
+
throw new Error('hypequery dev needs @hypequery/serve. Install it: npm install @hypequery/serve');
|
|
15
|
+
}
|
|
16
|
+
throw error;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { McpSession } from './telemetry/mcp-session.js';
|
|
2
|
+
interface HttpMcpDependencies {
|
|
3
|
+
loadMcp?: () => Promise<unknown>;
|
|
4
|
+
env?: Readonly<Record<string, string | undefined>>;
|
|
5
|
+
}
|
|
6
|
+
type HttpMcpPackage = Pick<typeof import('@hypequery/mcp'), 'createMCPHttpExecutor' | 'createMCPProtocolServer' | 'connectMCPServerStdio'>;
|
|
7
|
+
/** Load the user's optional peer without requiring or importing an analytics entrypoint. */
|
|
8
|
+
export declare function loadHttpMcp(load?: () => Promise<unknown>): Promise<HttpMcpPackage>;
|
|
9
|
+
/** One backend principal per process. Python resolves authorization and tenant context. */
|
|
10
|
+
export declare function runHttpMcp(url: string, selfTest: boolean, dependencies: HttpMcpDependencies, telemetry: McpSession): Promise<void>;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=mcp-http.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp-http.d.ts","sourceRoot":"","sources":["../../src/utils/mcp-http.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,4BAA4B,CAAC;AAE7D,UAAU,mBAAmB;IAC3B,OAAO,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,CAAC;IACjC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,CAAC;CACpD;AACD,KAAK,cAAc,GAAG,IAAI,CAAC,cAAc,gBAAgB,CAAC,EAAE,uBAAuB,GAAG,yBAAyB,GAAG,uBAAuB,CAAC,CAAC;AAE3I,4FAA4F;AAC5F,wBAAsB,WAAW,CAAC,IAAI,CAAC,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,cAAc,CAAC,CAUxF;AAED,2FAA2F;AAC3F,wBAAsB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,mBAAmB,EAAE,SAAS,EAAE,UAAU,GAAG,OAAO,CAAC,IAAI,CAAC,CAgCxI"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import path from 'node:path';
|
|
2
|
+
import { importFromProject } from './project-root.js';
|
|
3
|
+
import { runMcpUntilSignal } from './mcp-lifecycle.js';
|
|
4
|
+
import { MCP_API_KEY_VARIABLE } from './hosted-endpoints.js';
|
|
5
|
+
import { logger } from './logger.js';
|
|
6
|
+
/** Load the user's optional peer without requiring or importing an analytics entrypoint. */
|
|
7
|
+
export async function loadHttpMcp(load) {
|
|
8
|
+
let candidate;
|
|
9
|
+
try {
|
|
10
|
+
candidate = await (load ?? (() => importFromProject('@hypequery/mcp', path.join(process.cwd(), '__hypequery_mcp__.js'), () => import('@hypequery/mcp'))))();
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
throw new Error('Install @hypequery/mcp with HTTP client-contract support in this project.');
|
|
14
|
+
}
|
|
15
|
+
const module = candidate;
|
|
16
|
+
if (!module || typeof module.createMCPHttpExecutor !== 'function' || typeof module.createMCPProtocolServer !== 'function' || typeof module.connectMCPServerStdio !== 'function') {
|
|
17
|
+
throw new Error('Upgrade @hypequery/mcp in this project to a version with createMCPHttpExecutor support.');
|
|
18
|
+
}
|
|
19
|
+
return module;
|
|
20
|
+
}
|
|
21
|
+
/** One backend principal per process. Python resolves authorization and tenant context. */
|
|
22
|
+
export async function runHttpMcp(url, selfTest, dependencies, telemetry) {
|
|
23
|
+
const module = await loadHttpMcp(dependencies.loadMcp);
|
|
24
|
+
const key = (dependencies.env ?? process.env)[MCP_API_KEY_VARIABLE]?.trim();
|
|
25
|
+
const create = async (signal) => {
|
|
26
|
+
try {
|
|
27
|
+
return await module.createMCPHttpExecutor({ url, ...(key ? { headers: { authorization: `Bearer ${key}` } } : {}) }, signal);
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
if (signal?.aborted)
|
|
31
|
+
throw signal.reason;
|
|
32
|
+
// Do not echo a URL, credential, backend response or user-installed peer's error.
|
|
33
|
+
throw new Error('Could not initialize the HTTP MCP bridge. Check the discovery URL, backend access and client-contract compatibility.');
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
if (selfTest) {
|
|
37
|
+
const executor = await create();
|
|
38
|
+
let tools;
|
|
39
|
+
try {
|
|
40
|
+
tools = await executor.listTools();
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
throw new Error('HTTP MCP discovery check failed. Check backend access and reconnect after contract changes.');
|
|
44
|
+
}
|
|
45
|
+
logger.success('Authorized HTTP client contract is ready for MCP.');
|
|
46
|
+
logger.info(`Tools: ${tools.tools.length}`);
|
|
47
|
+
logger.info('No query tool was called. Python resolves the principal and tenant.');
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
await runMcpUntilSignal(async (signal) => {
|
|
51
|
+
const executor = await create(signal);
|
|
52
|
+
signal.throwIfAborted();
|
|
53
|
+
const server = module.createMCPProtocolServer({ executor: telemetry.instrument(executor) });
|
|
54
|
+
try {
|
|
55
|
+
await module.connectMCPServerStdio(server);
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
await server.close().catch(() => undefined);
|
|
59
|
+
throw new Error('Could not connect the MCP stdio transport.');
|
|
60
|
+
}
|
|
61
|
+
telemetry.start({ mode: 'serve', tenant_used: false, http_backend: true });
|
|
62
|
+
process.stderr.write('hypequery MCP serving an authorized HTTP client contract.\n');
|
|
63
|
+
return server;
|
|
64
|
+
});
|
|
65
|
+
}
|
|
@@ -2,5 +2,5 @@ export interface CloseableMcpServer {
|
|
|
2
2
|
close(): Promise<void>;
|
|
3
3
|
}
|
|
4
4
|
/** Own signal listeners for exactly the lifetime of the stdio server. */
|
|
5
|
-
export declare function runMcpUntilSignal(start: () => Promise<CloseableMcpServer>): Promise<void>;
|
|
5
|
+
export declare function runMcpUntilSignal(start: (signal: AbortSignal) => Promise<CloseableMcpServer>): Promise<void>;
|
|
6
6
|
//# sourceMappingURL=mcp-lifecycle.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-lifecycle.d.ts","sourceRoot":"","sources":["../../src/utils/mcp-lifecycle.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,yEAAyE;AACzE,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,MAAM,OAAO,CAAC,kBAAkB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"mcp-lifecycle.d.ts","sourceRoot":"","sources":["../../src/utils/mcp-lifecycle.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,yEAAyE;AACzE,wBAAsB,iBAAiB,CAAC,KAAK,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAC,kBAAkB,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAoBlH"}
|
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
/** Own signal listeners for exactly the lifetime of the stdio server. */
|
|
2
2
|
export async function runMcpUntilSignal(start) {
|
|
3
|
+
const controller = new AbortController();
|
|
3
4
|
let stop;
|
|
4
|
-
const stopped = new Promise(resolve => { stop = resolve; });
|
|
5
|
+
const stopped = new Promise(resolve => { stop = () => { controller.abort(); resolve(); }; });
|
|
5
6
|
// Install before awaiting startup so a signal during connection is retained.
|
|
6
7
|
process.on('SIGINT', stop);
|
|
7
8
|
process.on('SIGTERM', stop);
|
|
8
9
|
let server;
|
|
9
10
|
try {
|
|
10
|
-
|
|
11
|
+
try {
|
|
12
|
+
server = await start(controller.signal);
|
|
13
|
+
}
|
|
14
|
+
catch (error) {
|
|
15
|
+
if (controller.signal.aborted && error === controller.signal.reason)
|
|
16
|
+
return;
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
11
19
|
await stopped;
|
|
12
20
|
}
|
|
13
21
|
finally {
|
|
@@ -111,7 +111,7 @@ export declare const COMMAND_PROPERTIES: {
|
|
|
111
111
|
readonly help_topic: {
|
|
112
112
|
readonly kind: "enum";
|
|
113
113
|
readonly description: string;
|
|
114
|
-
readonly values: readonly ["root", "init", "dev", "mcp", "generate", "generate:types", "generate:datasets", "generate:manifest", "login", "logout", "deploy", "deployment:build", "deployment:validate", "deployment:release", "deployment:submit", "deployment:status", "pull", "diff", "help", "version", "telemetry", "unknown"];
|
|
114
|
+
readonly values: readonly ["root", "init", "dev", "mcp", "generate", "generate:types", "generate:datasets", "generate:manifest", "generate:client", "login", "logout", "deploy", "deployment:build", "deployment:validate", "deployment:release", "deployment:submit", "deployment:status", "pull", "diff", "help", "version", "telemetry", "unknown"];
|
|
115
115
|
} & {
|
|
116
116
|
readonly optional: true;
|
|
117
117
|
};
|
|
@@ -259,6 +259,12 @@ export declare const COMMAND_PROPERTIES: {
|
|
|
259
259
|
} & {
|
|
260
260
|
readonly optional: true;
|
|
261
261
|
};
|
|
262
|
+
readonly http_backend: {
|
|
263
|
+
readonly kind: "boolean";
|
|
264
|
+
readonly description: string;
|
|
265
|
+
} & {
|
|
266
|
+
readonly optional: true;
|
|
267
|
+
};
|
|
262
268
|
readonly dataset_count_bucket: {
|
|
263
269
|
readonly kind: "enum";
|
|
264
270
|
readonly description: string;
|
|
@@ -436,6 +442,41 @@ export declare const COMMAND_PROPERTIES: {
|
|
|
436
442
|
readonly optional: true;
|
|
437
443
|
};
|
|
438
444
|
};
|
|
445
|
+
readonly 'generate:client': {
|
|
446
|
+
readonly mode: {
|
|
447
|
+
readonly kind: "enum";
|
|
448
|
+
readonly description: string;
|
|
449
|
+
readonly values: readonly ["write", "check"];
|
|
450
|
+
} & {
|
|
451
|
+
readonly optional: true;
|
|
452
|
+
};
|
|
453
|
+
readonly check_result: {
|
|
454
|
+
readonly kind: "enum";
|
|
455
|
+
readonly description: string;
|
|
456
|
+
readonly values: readonly ["up_to_date", "out_of_date", "missing"];
|
|
457
|
+
} & {
|
|
458
|
+
readonly optional: true;
|
|
459
|
+
};
|
|
460
|
+
readonly force: {
|
|
461
|
+
readonly kind: "boolean";
|
|
462
|
+
readonly description: string;
|
|
463
|
+
} & {
|
|
464
|
+
readonly optional: true;
|
|
465
|
+
};
|
|
466
|
+
readonly custom_output: {
|
|
467
|
+
readonly kind: "boolean";
|
|
468
|
+
readonly description: string;
|
|
469
|
+
} & {
|
|
470
|
+
readonly optional: true;
|
|
471
|
+
};
|
|
472
|
+
readonly endpoint_count_bucket: {
|
|
473
|
+
readonly kind: "enum";
|
|
474
|
+
readonly description: string;
|
|
475
|
+
readonly values: readonly ["0", "1", "2-5", "6-20", "21-100", "101+"];
|
|
476
|
+
} & {
|
|
477
|
+
readonly optional: true;
|
|
478
|
+
};
|
|
479
|
+
};
|
|
439
480
|
readonly 'generate:manifest': {
|
|
440
481
|
readonly query_count_bucket: {
|
|
441
482
|
readonly kind: "enum";
|
|
@@ -497,6 +538,14 @@ export declare const SESSION_START_PROPERTIES: {
|
|
|
497
538
|
readonly kind: "enum";
|
|
498
539
|
readonly description: string;
|
|
499
540
|
readonly values: readonly ["hypequery.ts", "api.ts", "queries.ts", "explicit_file", "unknown"];
|
|
541
|
+
} & {
|
|
542
|
+
readonly optional: true;
|
|
543
|
+
};
|
|
544
|
+
readonly http_backend: {
|
|
545
|
+
readonly kind: "boolean";
|
|
546
|
+
readonly description: string;
|
|
547
|
+
} & {
|
|
548
|
+
readonly optional: true;
|
|
500
549
|
};
|
|
501
550
|
readonly mode: {
|
|
502
551
|
readonly kind: "enum";
|
|
@@ -507,6 +556,8 @@ export declare const SESSION_START_PROPERTIES: {
|
|
|
507
556
|
readonly kind: "enum";
|
|
508
557
|
readonly description: string;
|
|
509
558
|
readonly values: readonly ["0", "1", "2-5", "6-20", "21-100", "101+"];
|
|
559
|
+
} & {
|
|
560
|
+
readonly optional: true;
|
|
510
561
|
};
|
|
511
562
|
readonly tenant_used: {
|
|
512
563
|
readonly kind: "boolean";
|
|
@@ -516,6 +567,8 @@ export declare const SESSION_START_PROPERTIES: {
|
|
|
516
567
|
readonly kind: "enum";
|
|
517
568
|
readonly description: string;
|
|
518
569
|
readonly values: readonly ["0", "1", "2-5", "6-20", "21-100", "101+"];
|
|
570
|
+
} & {
|
|
571
|
+
readonly optional: true;
|
|
519
572
|
};
|
|
520
573
|
};
|
|
521
574
|
};
|
|
@@ -567,12 +620,12 @@ export declare const EVENT_CATALOG: {
|
|
|
567
620
|
readonly command: {
|
|
568
621
|
readonly kind: "enum";
|
|
569
622
|
readonly description: string;
|
|
570
|
-
readonly values: readonly ["init", "dev", "mcp", "generate", "generate:types", "generate:datasets", "generate:manifest", "login", "logout", "deploy", "deployment:build", "deployment:validate", "deployment:release", "deployment:submit", "deployment:status", "pull", "diff", "help", "version", "telemetry", "unknown"];
|
|
623
|
+
readonly values: readonly ["init", "dev", "mcp", "generate", "generate:types", "generate:datasets", "generate:manifest", "generate:client", "login", "logout", "deploy", "deployment:build", "deployment:validate", "deployment:release", "deployment:submit", "deployment:status", "pull", "diff", "help", "version", "telemetry", "unknown"];
|
|
571
624
|
};
|
|
572
625
|
readonly flags_used: {
|
|
573
626
|
readonly kind: "enum_list";
|
|
574
627
|
readonly description: string;
|
|
575
|
-
readonly values: readonly ("--path" | "--style" | "--database" | "--chdb-path" | "--auth" | "--all-tables" | "--tables" | "--exclude-tables" | "--no-example" | "--no-interactive" | "--force" | "--skip-connection" | "--port" | "--hostname" | "--no-watch" | "--no-cache" | "--cache" | "--redis-url" | "--open" | "--cors" | "--quiet" | "--tenant" | "--self-test" | "--url" | "--output" | "--tenant-column" | "--check" | "--diff" | "--cloud-url" | "--environment" | "--bundle-output" | "--release-output" | "--project" | "--no-source" | "--endpoint" | "--replace-restored" | "--mcp-config" | "--release" | "--no-telemetry" | "--help" | "--version")[];
|
|
628
|
+
readonly values: readonly ("--path" | "--style" | "--database" | "--chdb-path" | "--auth" | "--all-tables" | "--tables" | "--exclude-tables" | "--no-example" | "--no-interactive" | "--force" | "--skip-connection" | "--port" | "--hostname" | "--no-watch" | "--no-cache" | "--cache" | "--redis-url" | "--open" | "--cors" | "--quiet" | "--tenant" | "--self-test" | "--url" | "--client-contract-url" | "--output" | "--tenant-column" | "--check" | "--diff" | "--cloud-url" | "--environment" | "--bundle-output" | "--release-output" | "--project" | "--no-source" | "--endpoint" | "--replace-restored" | "--mcp-config" | "--release" | "--no-telemetry" | "--help" | "--version")[];
|
|
576
629
|
};
|
|
577
630
|
readonly outcome: {
|
|
578
631
|
readonly kind: "enum";
|
|
@@ -597,7 +650,7 @@ export declare const EVENT_CATALOG: {
|
|
|
597
650
|
readonly help_topic: {
|
|
598
651
|
readonly kind: "enum";
|
|
599
652
|
readonly description: string;
|
|
600
|
-
readonly values: readonly ["root", "init", "dev", "mcp", "generate", "generate:types", "generate:datasets", "generate:manifest", "login", "logout", "deploy", "deployment:build", "deployment:validate", "deployment:release", "deployment:submit", "deployment:status", "pull", "diff", "help", "version", "telemetry", "unknown"];
|
|
653
|
+
readonly values: readonly ["root", "init", "dev", "mcp", "generate", "generate:types", "generate:datasets", "generate:manifest", "generate:client", "login", "logout", "deploy", "deployment:build", "deployment:validate", "deployment:release", "deployment:submit", "deployment:status", "pull", "diff", "help", "version", "telemetry", "unknown"];
|
|
601
654
|
} & {
|
|
602
655
|
readonly optional: true;
|
|
603
656
|
};
|
|
@@ -745,6 +798,12 @@ export declare const EVENT_CATALOG: {
|
|
|
745
798
|
} & {
|
|
746
799
|
readonly optional: true;
|
|
747
800
|
};
|
|
801
|
+
readonly http_backend: {
|
|
802
|
+
readonly kind: "boolean";
|
|
803
|
+
readonly description: string;
|
|
804
|
+
} & {
|
|
805
|
+
readonly optional: true;
|
|
806
|
+
};
|
|
748
807
|
readonly dataset_count_bucket: {
|
|
749
808
|
readonly kind: "enum";
|
|
750
809
|
readonly description: string;
|
|
@@ -922,6 +981,41 @@ export declare const EVENT_CATALOG: {
|
|
|
922
981
|
readonly optional: true;
|
|
923
982
|
};
|
|
924
983
|
};
|
|
984
|
+
readonly 'generate:client': {
|
|
985
|
+
readonly mode: {
|
|
986
|
+
readonly kind: "enum";
|
|
987
|
+
readonly description: string;
|
|
988
|
+
readonly values: readonly ["write", "check"];
|
|
989
|
+
} & {
|
|
990
|
+
readonly optional: true;
|
|
991
|
+
};
|
|
992
|
+
readonly check_result: {
|
|
993
|
+
readonly kind: "enum";
|
|
994
|
+
readonly description: string;
|
|
995
|
+
readonly values: readonly ["up_to_date", "out_of_date", "missing"];
|
|
996
|
+
} & {
|
|
997
|
+
readonly optional: true;
|
|
998
|
+
};
|
|
999
|
+
readonly force: {
|
|
1000
|
+
readonly kind: "boolean";
|
|
1001
|
+
readonly description: string;
|
|
1002
|
+
} & {
|
|
1003
|
+
readonly optional: true;
|
|
1004
|
+
};
|
|
1005
|
+
readonly custom_output: {
|
|
1006
|
+
readonly kind: "boolean";
|
|
1007
|
+
readonly description: string;
|
|
1008
|
+
} & {
|
|
1009
|
+
readonly optional: true;
|
|
1010
|
+
};
|
|
1011
|
+
readonly endpoint_count_bucket: {
|
|
1012
|
+
readonly kind: "enum";
|
|
1013
|
+
readonly description: string;
|
|
1014
|
+
readonly values: readonly ["0", "1", "2-5", "6-20", "21-100", "101+"];
|
|
1015
|
+
} & {
|
|
1016
|
+
readonly optional: true;
|
|
1017
|
+
};
|
|
1018
|
+
};
|
|
925
1019
|
readonly 'generate:manifest': {
|
|
926
1020
|
readonly query_count_bucket: {
|
|
927
1021
|
readonly kind: "enum";
|
|
@@ -993,6 +1087,14 @@ export declare const EVENT_CATALOG: {
|
|
|
993
1087
|
readonly kind: "enum";
|
|
994
1088
|
readonly description: string;
|
|
995
1089
|
readonly values: readonly ["hypequery.ts", "api.ts", "queries.ts", "explicit_file", "unknown"];
|
|
1090
|
+
} & {
|
|
1091
|
+
readonly optional: true;
|
|
1092
|
+
};
|
|
1093
|
+
readonly http_backend: {
|
|
1094
|
+
readonly kind: "boolean";
|
|
1095
|
+
readonly description: string;
|
|
1096
|
+
} & {
|
|
1097
|
+
readonly optional: true;
|
|
996
1098
|
};
|
|
997
1099
|
readonly mode: {
|
|
998
1100
|
readonly kind: "enum";
|
|
@@ -1003,6 +1105,8 @@ export declare const EVENT_CATALOG: {
|
|
|
1003
1105
|
readonly kind: "enum";
|
|
1004
1106
|
readonly description: string;
|
|
1005
1107
|
readonly values: readonly ["0", "1", "2-5", "6-20", "21-100", "101+"];
|
|
1108
|
+
} & {
|
|
1109
|
+
readonly optional: true;
|
|
1006
1110
|
};
|
|
1007
1111
|
readonly tenant_used: {
|
|
1008
1112
|
readonly kind: "boolean";
|
|
@@ -1012,6 +1116,8 @@ export declare const EVENT_CATALOG: {
|
|
|
1012
1116
|
readonly kind: "enum";
|
|
1013
1117
|
readonly description: string;
|
|
1014
1118
|
readonly values: readonly ["0", "1", "2-5", "6-20", "21-100", "101+"];
|
|
1119
|
+
} & {
|
|
1120
|
+
readonly optional: true;
|
|
1015
1121
|
};
|
|
1016
1122
|
};
|
|
1017
1123
|
};
|
|
@@ -1083,7 +1189,7 @@ export declare const EVENT_CATALOG: {
|
|
|
1083
1189
|
readonly command: {
|
|
1084
1190
|
readonly kind: "enum";
|
|
1085
1191
|
readonly description: string;
|
|
1086
|
-
readonly values: readonly ["init", "dev", "mcp", "generate", "generate:types", "generate:datasets", "generate:manifest", "login", "logout", "deploy", "deployment:build", "deployment:validate", "deployment:release", "deployment:submit", "deployment:status", "pull", "diff", "help", "version", "telemetry", "unknown"];
|
|
1192
|
+
readonly values: readonly ["init", "dev", "mcp", "generate", "generate:types", "generate:datasets", "generate:manifest", "generate:client", "login", "logout", "deploy", "deployment:build", "deployment:validate", "deployment:release", "deployment:submit", "deployment:status", "pull", "diff", "help", "version", "telemetry", "unknown"];
|
|
1087
1193
|
};
|
|
1088
1194
|
readonly exception_class: {
|
|
1089
1195
|
readonly kind: "enum";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../../src/utils/telemetry/catalog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,aAAa,EAA2D,YAAY,EAA8D,MAAM,cAAc,CAAC;AAC1L,OAAO,EAA0E,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAGnI,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAMtC,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoBH,CAAC;AAc5B,oFAAoF;AACpF,eAAO,MAAM,kBAAkB
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../../src/utils/telemetry/catalog.ts"],"names":[],"mappings":"AAAA,OAAO,EAAY,aAAa,EAA2D,YAAY,EAA8D,MAAM,cAAc,CAAC;AAC1L,OAAO,EAA0E,KAAK,MAAM,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAGnI,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAMtC,eAAO,MAAM,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAoBH,CAAC;AAc5B,oFAAoF;AACpF,eAAO,MAAM,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyD6B,CAAC;AAE7D,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkB3B,CAAC;AACX,eAAO,MAAM,sBAAsB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAUzB,CAAC;AAEX,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkChB,CAAC;AAEX,MAAM,MAAM,kBAAkB,GAAG,MAAM,OAAO,aAAa,CAAC;AAC5D,KAAK,UAAU,CAAC,CAAC,SAAS,kBAAkB,IAAI,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC;AACxE,KAAK,iBAAiB,CAAC,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI;KACxD,CAAC,IAAI,MAAM,CAAC,GAAG;QAAE,OAAO,EAAE,CAAC,CAAA;KAAE,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;CAClD,CAAC,MAAM,CAAC,CAAC,CAAC;AACX,KAAK,eAAe,GAAG;KACpB,CAAC,IAAI,MAAM,OAAO,aAAa,GAAG;QAAE,OAAO,EAAE,CAAC,CAAC;QAAC,UAAU,EAAE,SAAS,CAAC,OAAO,aAAa,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC,EAAE,CAAA;KAAE;CAC1I,CAAC,MAAM,OAAO,aAAa,CAAC,CAAC;AAC9B,MAAM,MAAM,mBAAmB,CAAC,CAAC,SAAS,kBAAkB,IAAI,UAAU,CAAC,OAAO,iBAAiB,CAAC,GAChG,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,GACvC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS;IAAE,QAAQ,EAAE,MAAM,CAAC,SAAS,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,GAAG,iBAAiB,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,GAC7G,CAAC,CAAC,SAAS,uBAAuB,GAAG,eAAe,GAAG,OAAO,CAAC,CAAC;AACpE,MAAM,MAAM,cAAc,GAAG;KAC1B,CAAC,IAAI,kBAAkB,GAAG;QAAE,QAAQ,CAAC,KAAK,EAAE,CAAC,CAAC;QAAC,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAC,CAAC,CAAC,CAAA;KAAE;CAC9F,CAAC,kBAAkB,CAAC,CAAC"}
|
|
@@ -36,7 +36,7 @@ const generation = {
|
|
|
36
36
|
unsupported_type_families: optional(enumList('Which built-in type families need mapping? Never full type expressions.', TYPE_FAMILIES)),
|
|
37
37
|
custom_output: optional(output),
|
|
38
38
|
};
|
|
39
|
-
const INSTRUMENTED = ['init', 'dev', 'mcp', 'generate', 'generate:types', 'generate:datasets', 'generate:manifest', 'help'];
|
|
39
|
+
const INSTRUMENTED = ['init', 'dev', 'mcp', 'generate', 'generate:types', 'generate:datasets', 'generate:manifest', 'generate:client', 'help'];
|
|
40
40
|
/** Command-specific completion fields; absence means that stage was not reached. */
|
|
41
41
|
export const COMMAND_PROPERTIES = {
|
|
42
42
|
init: {
|
|
@@ -62,6 +62,7 @@ export const COMMAND_PROPERTIES = {
|
|
|
62
62
|
mcp: {
|
|
63
63
|
mode: optional(enumeration('Is MCP used for validation or serving?', ['self_test', 'serve'])),
|
|
64
64
|
hosted: optional(boolean('Is MCP self-test targeting a hosted endpoint?')),
|
|
65
|
+
http_backend: optional(boolean('Is MCP using an authorized HTTP client contract? Never its URL or principal.')),
|
|
65
66
|
dataset_count_bucket: optional(count('How many datasets are exposed to agents?')),
|
|
66
67
|
tenant_used: optional(boolean('Is a trusted tenant supplied? Never its value.')),
|
|
67
68
|
tenant_dataset_count_bucket: optional(count('How many datasets require tenant scope?')),
|
|
@@ -78,6 +79,13 @@ export const COMMAND_PROPERTIES = {
|
|
|
78
79
|
datasets_generated_bucket: optional(count('How many dataset definitions are generated?')),
|
|
79
80
|
warning_counts: optional(record('Which tenant-scaffolding warnings need improvement?', fieldsFor(WARNING_CODES, optional(count('Bucketed warning occurrences.'))))),
|
|
80
81
|
},
|
|
82
|
+
'generate:client': {
|
|
83
|
+
mode: optional(enumeration('Are client types generated or checked in CI?', ['write', 'check'])),
|
|
84
|
+
check_result: optional(enumeration('Does CI find stale client types?', ['up_to_date', 'out_of_date', 'missing'])),
|
|
85
|
+
force: optional(boolean('Are generated client types replaced?')),
|
|
86
|
+
custom_output: optional(output),
|
|
87
|
+
endpoint_count_bucket: optional(count('How many public endpoints need client types?')),
|
|
88
|
+
},
|
|
81
89
|
'generate:manifest': {
|
|
82
90
|
query_count_bucket: optional(count('How much query-based React integration is adopted?')),
|
|
83
91
|
endpoint_count_bucket: optional(count('How much endpoint-based React integration is adopted?')),
|
|
@@ -99,11 +107,12 @@ export const SESSION_START_PROPERTIES = {
|
|
|
99
107
|
quiet: boolean('Is quiet startup adopted?'),
|
|
100
108
|
},
|
|
101
109
|
mcp: {
|
|
102
|
-
entry_type: entry,
|
|
110
|
+
entry_type: optional(entry),
|
|
111
|
+
http_backend: optional(boolean('Is MCP using an authorized HTTP client contract? Never its URL or principal.')),
|
|
103
112
|
mode: enumeration('Is MCP serving or validating?', ['self_test', 'serve']),
|
|
104
|
-
dataset_count_bucket: count('How many datasets are exposed to agents?'),
|
|
113
|
+
dataset_count_bucket: optional(count('How many local datasets are exposed to agents? Omit for HTTP contracts.')),
|
|
105
114
|
tenant_used: boolean('Is tenant scope supplied? Never its value.'),
|
|
106
|
-
tenant_dataset_count_bucket: count('How many
|
|
115
|
+
tenant_dataset_count_bucket: optional(count('How many local datasets require tenant scope? Omit for HTTP contracts.')),
|
|
107
116
|
},
|
|
108
117
|
};
|
|
109
118
|
export const SESSION_END_PROPERTIES = {
|
|
@@ -4,17 +4,18 @@ export declare const DURATION_BUCKETS: readonly ["<100ms", "100ms-1s", "1s-10s",
|
|
|
4
4
|
export declare const ERROR_CODES: readonly ["unknown", "connection_failed", "auth_failed", "entrypoint_not_found", "compile_error", "prompt_cancelled", "validation_failed", "cloud_network_error", "cloud_rejected", "not_installed", "engine_error", "no_tables", "no_datasets", "tenant_required", "load_error", "output_missing", "output_out_of_date", "unknown_command", "unknown_option"];
|
|
5
5
|
export declare const OUTCOMES: readonly ["success", "failure", "cancelled", "interrupted"];
|
|
6
6
|
export declare const DATABASES: readonly ["clickhouse", "chdb", "bigquery", "unknown"];
|
|
7
|
-
export declare const COMMANDS: readonly ["init", "dev", "mcp", "generate", "generate:types", "generate:datasets", "generate:manifest", "login", "logout", "deploy", "deployment:build", "deployment:validate", "deployment:release", "deployment:submit", "deployment:status", "pull", "diff", "help", "version", "telemetry", "unknown"];
|
|
7
|
+
export declare const COMMANDS: readonly ["init", "dev", "mcp", "generate", "generate:types", "generate:datasets", "generate:manifest", "generate:client", "login", "logout", "deploy", "deployment:build", "deployment:validate", "deployment:release", "deployment:submit", "deployment:status", "pull", "diff", "help", "version", "telemetry", "unknown"];
|
|
8
8
|
export type TelemetryCommand = typeof COMMANDS[number];
|
|
9
9
|
/** Canonical long names, including negated flags. Values and aliases never leave the CLI. */
|
|
10
10
|
export declare const COMMAND_FLAGS: {
|
|
11
11
|
readonly init: readonly ["--path", "--style", "--database", "--chdb-path", "--auth", "--all-tables", "--tables", "--exclude-tables", "--no-example", "--no-interactive", "--force", "--skip-connection"];
|
|
12
12
|
readonly dev: readonly ["--port", "--hostname", "--no-watch", "--no-cache", "--cache", "--redis-url", "--open", "--cors", "--path", "--quiet"];
|
|
13
|
-
readonly mcp: readonly ["--path", "--tenant", "--self-test", "--url"];
|
|
13
|
+
readonly mcp: readonly ["--path", "--tenant", "--self-test", "--url", "--client-contract-url"];
|
|
14
14
|
readonly generate: readonly ["--output", "--path", "--tables", "--database", "--chdb-path"];
|
|
15
15
|
readonly 'generate:types': readonly ["--output", "--path", "--tables", "--database", "--chdb-path"];
|
|
16
16
|
readonly 'generate:datasets': readonly ["--output", "--path", "--tables", "--exclude-tables", "--tenant-column", "--force", "--check", "--diff"];
|
|
17
17
|
readonly 'generate:manifest': readonly ["--output"];
|
|
18
|
+
readonly 'generate:client': readonly ["--output", "--check", "--force"];
|
|
18
19
|
readonly login: readonly ["--cloud-url", "--environment"];
|
|
19
20
|
readonly logout: readonly [];
|
|
20
21
|
readonly deploy: readonly ["--bundle-output", "--release-output", "--project", "--environment", "--no-source", "--endpoint", "--replace-restored", "--mcp-config"];
|
|
@@ -35,11 +36,12 @@ export declare const GLOBAL_FLAGS: readonly ["--no-telemetry", "--help", "--vers
|
|
|
35
36
|
export declare const VALUE_FLAGS: {
|
|
36
37
|
readonly init: readonly ["--path", "--style", "--database", "--chdb-path", "--auth", "--tables", "--exclude-tables"];
|
|
37
38
|
readonly dev: readonly ["--port", "--hostname", "--cache", "--redis-url", "--path"];
|
|
38
|
-
readonly mcp: readonly ["--path", "--tenant", "--url"];
|
|
39
|
+
readonly mcp: readonly ["--path", "--tenant", "--url", "--client-contract-url"];
|
|
39
40
|
readonly generate: readonly ["--output", "--path", "--tables", "--database", "--chdb-path"];
|
|
40
41
|
readonly 'generate:types': readonly ["--output", "--path", "--tables", "--database", "--chdb-path"];
|
|
41
42
|
readonly 'generate:datasets': readonly ["--output", "--path", "--tables", "--exclude-tables", "--tenant-column"];
|
|
42
43
|
readonly 'generate:manifest': readonly ["--output"];
|
|
44
|
+
readonly 'generate:client': readonly ["--output"];
|
|
43
45
|
readonly login: readonly ["--cloud-url", "--environment"];
|
|
44
46
|
readonly logout: readonly [];
|
|
45
47
|
readonly deploy: readonly ["--bundle-output", "--release-output", "--project", "--environment", "--endpoint"];
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"domains.d.ts","sourceRoot":"","sources":["../../../src/utils/telemetry/domains.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,eAAO,MAAM,aAAa,sDAAuD,CAAC;AAClF,eAAO,MAAM,gBAAgB,gFAAiF,CAAC;AAC/G,eAAO,MAAM,WAAW,gWAKd,CAAC;AACX,eAAO,MAAM,QAAQ,6DAA8D,CAAC;AACpF,eAAO,MAAM,SAAS,wDAAyD,CAAC;AAChF,eAAO,MAAM,QAAQ,
|
|
1
|
+
{"version":3,"file":"domains.d.ts","sourceRoot":"","sources":["../../../src/utils/telemetry/domains.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,eAAO,MAAM,aAAa,sDAAuD,CAAC;AAClF,eAAO,MAAM,gBAAgB,gFAAiF,CAAC;AAC/G,eAAO,MAAM,WAAW,gWAKd,CAAC;AACX,eAAO,MAAM,QAAQ,6DAA8D,CAAC;AACpF,eAAO,MAAM,SAAS,wDAAyD,CAAC;AAChF,eAAO,MAAM,QAAQ,+TAKX,CAAC;AACX,MAAM,MAAM,gBAAgB,GAAG,OAAO,QAAQ,CAAC,MAAM,CAAC,CAAC;AAEvD,6FAA6F;AAC7F,eAAO,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;CAoBsC,CAAC;AACjE,eAAO,MAAM,YAAY,oDAAqD,CAAC;AAC/E,yFAAyF;AACzF,eAAO,MAAM,WAAW;;;;;;;;;;;;;;;;;;;;;;;CAoBoE,CAAC;AAC7F,MAAM,MAAM,aAAa,GAAG,OAAO,aAAa,CAAC,gBAAgB,CAAC,CAAC,MAAM,CAAC,GAAG,OAAO,YAAY,CAAC,MAAM,CAAC,CAAC;AACzG,eAAO,MAAM,WAAW;;;;;;CAAwG,CAAC;AACjI,eAAO,MAAM,kBAAkB,0NAA2N,CAAC;AAC3P,eAAO,MAAM,aAAa,4DAA6D,CAAC;AACxF,eAAO,MAAM,aAAa,4NAA6N,CAAC"}
|
|
@@ -11,7 +11,7 @@ export const OUTCOMES = ['success', 'failure', 'cancelled', 'interrupted'];
|
|
|
11
11
|
export const DATABASES = ['clickhouse', 'chdb', 'bigquery', 'unknown'];
|
|
12
12
|
export const COMMANDS = [
|
|
13
13
|
'init', 'dev', 'mcp', 'generate', 'generate:types', 'generate:datasets',
|
|
14
|
-
'generate:manifest', 'login', 'logout', 'deploy', 'deployment:build',
|
|
14
|
+
'generate:manifest', 'generate:client', 'login', 'logout', 'deploy', 'deployment:build',
|
|
15
15
|
'deployment:validate', 'deployment:release', 'deployment:submit', 'deployment:status',
|
|
16
16
|
'pull', 'diff', 'help', 'version', 'telemetry', 'unknown',
|
|
17
17
|
];
|
|
@@ -19,11 +19,12 @@ export const COMMANDS = [
|
|
|
19
19
|
export const COMMAND_FLAGS = {
|
|
20
20
|
init: ['--path', '--style', '--database', '--chdb-path', '--auth', '--all-tables', '--tables', '--exclude-tables', '--no-example', '--no-interactive', '--force', '--skip-connection'],
|
|
21
21
|
dev: ['--port', '--hostname', '--no-watch', '--no-cache', '--cache', '--redis-url', '--open', '--cors', '--path', '--quiet'],
|
|
22
|
-
mcp: ['--path', '--tenant', '--self-test', '--url'],
|
|
22
|
+
mcp: ['--path', '--tenant', '--self-test', '--url', '--client-contract-url'],
|
|
23
23
|
generate: ['--output', '--path', '--tables', '--database', '--chdb-path'],
|
|
24
24
|
'generate:types': ['--output', '--path', '--tables', '--database', '--chdb-path'],
|
|
25
25
|
'generate:datasets': ['--output', '--path', '--tables', '--exclude-tables', '--tenant-column', '--force', '--check', '--diff'],
|
|
26
26
|
'generate:manifest': ['--output'],
|
|
27
|
+
'generate:client': ['--output', '--check', '--force'],
|
|
27
28
|
login: ['--cloud-url', '--environment'],
|
|
28
29
|
logout: [],
|
|
29
30
|
deploy: ['--bundle-output', '--release-output', '--project', '--environment', '--no-source', '--endpoint', '--replace-restored', '--mcp-config'],
|
|
@@ -41,11 +42,12 @@ export const GLOBAL_FLAGS = ['--no-telemetry', '--help', '--version'];
|
|
|
41
42
|
export const VALUE_FLAGS = {
|
|
42
43
|
init: ['--path', '--style', '--database', '--chdb-path', '--auth', '--tables', '--exclude-tables'],
|
|
43
44
|
dev: ['--port', '--hostname', '--cache', '--redis-url', '--path'],
|
|
44
|
-
mcp: ['--path', '--tenant', '--url'],
|
|
45
|
+
mcp: ['--path', '--tenant', '--url', '--client-contract-url'],
|
|
45
46
|
generate: ['--output', '--path', '--tables', '--database', '--chdb-path'],
|
|
46
47
|
'generate:types': ['--output', '--path', '--tables', '--database', '--chdb-path'],
|
|
47
48
|
'generate:datasets': ['--output', '--path', '--tables', '--exclude-tables', '--tenant-column'],
|
|
48
49
|
'generate:manifest': ['--output'],
|
|
50
|
+
'generate:client': ['--output'],
|
|
49
51
|
login: ['--cloud-url', '--environment'],
|
|
50
52
|
logout: [],
|
|
51
53
|
deploy: ['--bundle-output', '--release-output', '--project', '--environment', '--endpoint'],
|
|
@@ -11,8 +11,8 @@ export class McpSession {
|
|
|
11
11
|
}
|
|
12
12
|
instrument(executor) {
|
|
13
13
|
return {
|
|
14
|
-
listTools:
|
|
15
|
-
getPrompt: (name, args) => executor.getPrompt(name, args), getManifestHash: () => executor.getManifestHash(),
|
|
14
|
+
listTools: signal => executor.listTools(signal), listPrompts: signal => executor.listPrompts(signal),
|
|
15
|
+
getPrompt: (name, args, signal) => executor.getPrompt(name, args, signal), getManifestHash: () => executor.getManifestHash(),
|
|
16
16
|
callTool: async (name, args, signal) => {
|
|
17
17
|
if (Object.hasOwn(toolKinds, name))
|
|
18
18
|
this.calls[toolKinds[name]]++;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hypequery/cli",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.25.0",
|
|
4
4
|
"description": "CLI for scaffolding type-safe ClickHouse analytics, semantic datasets, React manifests, and deployments",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"clickhouse",
|
|
@@ -22,19 +22,19 @@
|
|
|
22
22
|
"dist"
|
|
23
23
|
],
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"@clickhouse/client": "^1.24.
|
|
25
|
+
"@clickhouse/client": "^1.24.1",
|
|
26
26
|
"chalk": "^5.3.0",
|
|
27
|
-
"commander": "^
|
|
28
|
-
"dotenv": "^
|
|
27
|
+
"commander": "^15.0.0",
|
|
28
|
+
"dotenv": "^18.0.8",
|
|
29
29
|
"esbuild": "^0.28.2",
|
|
30
|
-
"open": "^
|
|
30
|
+
"open": "^11.0.4",
|
|
31
31
|
"ora": "^8.0.1",
|
|
32
32
|
"prompts": "^2.4.2",
|
|
33
|
-
"@hypequery/deployment": "0.
|
|
34
|
-
"@hypequery/protocol": "0.
|
|
33
|
+
"@hypequery/deployment": "0.10.0",
|
|
34
|
+
"@hypequery/protocol": "0.21.0"
|
|
35
35
|
},
|
|
36
36
|
"optionalDependencies": {
|
|
37
|
-
"@napi-rs/keyring": "1.
|
|
37
|
+
"@napi-rs/keyring": "2.1.2"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"@hypequery/mcp": "*",
|
|
@@ -43,16 +43,19 @@
|
|
|
43
43
|
"peerDependenciesMeta": {
|
|
44
44
|
"@hypequery/mcp": {
|
|
45
45
|
"optional": true
|
|
46
|
+
},
|
|
47
|
+
"@hypequery/serve": {
|
|
48
|
+
"optional": true
|
|
46
49
|
}
|
|
47
50
|
},
|
|
48
51
|
"devDependencies": {
|
|
49
52
|
"@types/node": "^20.11.30",
|
|
50
53
|
"@types/prompts": "^2.4.9",
|
|
51
|
-
"@vitest/coverage-v8": "^5.0.
|
|
54
|
+
"@vitest/coverage-v8": "^5.0.5",
|
|
52
55
|
"typescript": "^6.0.3",
|
|
53
|
-
"vitest": "^5.0.
|
|
54
|
-
"@hypequery/mcp": "0.
|
|
55
|
-
"@hypequery/serve": "0.
|
|
56
|
+
"vitest": "^5.0.5",
|
|
57
|
+
"@hypequery/mcp": "0.10.0",
|
|
58
|
+
"@hypequery/serve": "0.22.0"
|
|
56
59
|
},
|
|
57
60
|
"repository": {
|
|
58
61
|
"type": "git",
|