@hypequery/cli 1.9.5 → 1.10.1

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/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkBpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AAgLD,OAAO,EAAE,OAAO,EAAE,CAAC"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAkBpC,QAAA,MAAM,OAAO,SAAgB,CAAC;AAa9B,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;;EAKpE;AAkLD,OAAO,EAAE,OAAO,EAAE,CAAC"}
package/dist/cli.js CHANGED
@@ -78,6 +78,8 @@ program
78
78
  .option('--cache <provider>', 'Cache provider (memory|redis)')
79
79
  .option('--redis-url <url>', 'Redis connection URL')
80
80
  .option('--open', 'Open browser automatically')
81
+ .option('--ui-experimental', 'Serve the experimental query UI at /__dev (unstable, not for production use)')
82
+ .option('--no-telemetry', 'Disable anonymous playground usage telemetry')
81
83
  .option('--cors', 'Enable CORS')
82
84
  .option('--path <path>', 'Analytics directory (loads <path>/api.ts or <path>/queries.ts)')
83
85
  .option('-q, --quiet', 'Suppress startup messages')
@@ -8,6 +8,17 @@ export interface DevOptions {
8
8
  open?: boolean;
9
9
  cors?: boolean;
10
10
  path?: string;
11
+ /**
12
+ * Opt into the experimental query UI/gateway at /__dev. Off by default —
13
+ * the UI is not release-ready; behaviour and flag may change or go away.
14
+ */
15
+ uiExperimental?: boolean;
16
+ /**
17
+ * Anonymous playground usage telemetry. Commander sets this to `false` when
18
+ * `--no-telemetry` is passed; otherwise it is undefined (telemetry follows
19
+ * its usual endpoint/env gating).
20
+ */
21
+ telemetry?: boolean;
11
22
  }
12
23
  export declare function devCommand(file?: string, options?: DevOptions): Promise<void>;
13
24
  //# sourceMappingURL=dev.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AA8BA,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,iBAyLvE"}
1
+ {"version":3,"file":"dev.d.ts","sourceRoot":"","sources":["../../src/commands/dev.ts"],"names":[],"mappings":"AA8BA,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;IACd;;;OAGG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB;;;;OAIG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;CACrB;AA6BD,wBAAsB,UAAU,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,OAAO,GAAE,UAAe,iBAkQvE"}
@@ -26,6 +26,30 @@ function displayQueriesFileNotFoundError(commandName) {
26
26
  logger.indent(`hypequery ${commandName} ./path/to/api.ts`);
27
27
  logger.newline();
28
28
  }
29
+ /**
30
+ * Attempt to create the local gateway from @hypequery/gateway. The package is an
31
+ * optional dependency, so it may be absent; in that case we return null (with a
32
+ * hint) and `hypequery dev` still runs as a plain server.
33
+ */
34
+ async function createGatewayIfAvailable(api, telemetryDisabled) {
35
+ try {
36
+ const { createGateway } = await import('@hypequery/gateway');
37
+ return (await createGateway(api, {
38
+ projectName: path.basename(process.cwd()),
39
+ devToken: process.env.HYPEQUERY_DEV_TOKEN,
40
+ telemetryDisabled,
41
+ }));
42
+ }
43
+ catch (error) {
44
+ const message = error instanceof Error ? error.message : String(error);
45
+ if (message.includes('Cannot find') || message.includes('ERR_MODULE_NOT_FOUND')) {
46
+ logger.warn('Playground UI not installed. Install @hypequery/gateway to enable it.');
47
+ return null;
48
+ }
49
+ logger.warn(`Playground failed to start: ${message}`);
50
+ return null;
51
+ }
52
+ }
29
53
  export async function devCommand(file, options = {}) {
30
54
  // Step 1: Find queries file
31
55
  const queriesFile = file
@@ -40,7 +64,43 @@ export async function devCommand(file, options = {}) {
40
64
  logger.info(`Found: ${path.relative(process.cwd(), queriesFile)}`);
41
65
  logger.newline();
42
66
  let currentServer = null;
67
+ let currentGateway = null;
68
+ let lifecycleOperation = Promise.resolve();
69
+ let shutdownRequested = false;
43
70
  const shouldWatch = options.watch !== false; // Default to true
71
+ const queueLifecycleOperation = (operation) => {
72
+ const queued = lifecycleOperation.then(operation, operation);
73
+ lifecycleOperation = queued.catch(() => undefined);
74
+ return queued;
75
+ };
76
+ const stopCurrentRuntime = async () => {
77
+ const gateway = currentGateway;
78
+ const server = currentServer;
79
+ // Clear references before awaiting teardown so a second signal or restart
80
+ // cannot stop the same resources twice.
81
+ currentGateway = null;
82
+ currentServer = null;
83
+ let teardownError;
84
+ if (gateway) {
85
+ try {
86
+ await gateway.shutdown();
87
+ }
88
+ catch (error) {
89
+ teardownError = error;
90
+ }
91
+ }
92
+ if (server) {
93
+ try {
94
+ await server.stop();
95
+ }
96
+ catch (error) {
97
+ teardownError ??= error;
98
+ }
99
+ }
100
+ if (teardownError) {
101
+ throw teardownError;
102
+ }
103
+ };
44
104
  const startServer = async () => {
45
105
  // Loading spinners for slow operations
46
106
  const compileSpinner = ora('Compiling queries...').start();
@@ -67,12 +127,19 @@ export async function devCommand(file, options = {}) {
67
127
  logger.header('hypequery dev');
68
128
  logger.success(`Registered ${queryCount} ${queryCount === 1 ? 'query' : 'queries'}`);
69
129
  logger.newline();
130
+ // The query UI is experimental and strictly opt-in; the default dev
131
+ // server is unchanged.
132
+ const gateway = options.uiExperimental
133
+ ? await createGatewayIfAvailable(api, options.telemetry === false)
134
+ : null;
135
+ currentGateway = gateway;
70
136
  // Start the server
71
- const { serveDev } = await import('@hypequery/serve');
137
+ const { serveDev } = await import('@hypequery/serve/dev');
72
138
  currentServer = await serveDev(api, {
73
139
  port: options.port,
74
140
  hostname: options.hostname,
75
141
  quiet: true,
142
+ mount: gateway?.mount,
76
143
  });
77
144
  const address = currentServer.server.address();
78
145
  const port = typeof address === 'object' && address ? address.port : options.port || 4000;
@@ -80,10 +147,14 @@ export async function devCommand(file, options = {}) {
80
147
  const baseUrl = `http://${hostname}:${port}`;
81
148
  const docsPath = api.docs?.path ?? '/docs';
82
149
  const openapiPath = api.openapi?.path ?? '/openapi.json';
83
- logger.box([
150
+ const boxLines = [
84
151
  `Docs: ${baseUrl}${api.basePath ?? ''}${docsPath}`,
85
152
  `OpenAPI: ${baseUrl}${api.basePath ?? ''}${openapiPath}`,
86
- ]);
153
+ ];
154
+ if (gateway?.uiAvailable) {
155
+ boxLines.unshift(`Playground: ${baseUrl}/__dev`);
156
+ }
157
+ logger.box(boxLines);
87
158
  logger.newline();
88
159
  logger.success(`Ready in ${process.uptime().toFixed(0)}ms`);
89
160
  logger.newline();
@@ -110,6 +181,16 @@ export async function devCommand(file, options = {}) {
110
181
  }
111
182
  }
112
183
  catch (error) {
184
+ // Gateway creation happens before serveDev so its mount can be passed to
185
+ // the server. If server startup (or any later setup) fails, tear down all
186
+ // resources acquired by this attempt before retrying or exiting.
187
+ try {
188
+ await stopCurrentRuntime();
189
+ }
190
+ catch (teardownError) {
191
+ const message = teardownError instanceof Error ? teardownError.message : String(teardownError);
192
+ logger.warn(`Failed to clean up dev server resources: ${message}`);
193
+ }
113
194
  // Stop spinners if they're still running
114
195
  if (compileSpinner.isSpinning) {
115
196
  compileSpinner.fail('Failed to compile queries');
@@ -136,22 +217,29 @@ export async function devCommand(file, options = {}) {
136
217
  }
137
218
  }
138
219
  };
139
- const restartServer = async () => {
140
- if (currentServer) {
220
+ const restartServer = () => queueLifecycleOperation(async () => {
221
+ if (shutdownRequested)
222
+ return;
223
+ if (currentServer || currentGateway) {
141
224
  logger.newline();
142
225
  logger.reload('File changed, restarting...');
143
226
  logger.newline();
144
- await currentServer.stop();
227
+ await stopCurrentRuntime();
145
228
  }
146
- await startServer();
147
- };
148
- const shutdown = async () => {
229
+ if (!shutdownRequested) {
230
+ await startServer();
231
+ }
232
+ });
233
+ const shutdown = () => {
234
+ if (shutdownRequested)
235
+ return lifecycleOperation;
236
+ shutdownRequested = true;
149
237
  logger.newline();
150
238
  logger.info('Shutting down dev server...');
151
- if (currentServer) {
152
- await currentServer.stop();
153
- }
154
- process.exit(0);
239
+ return queueLifecycleOperation(async () => {
240
+ await stopCurrentRuntime();
241
+ process.exit(0);
242
+ });
155
243
  };
156
244
  // Start initial server
157
245
  await startServer();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hypequery/cli",
3
- "version": "1.9.5",
3
+ "version": "1.10.1",
4
4
  "description": "Command-line interface for hypequery",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -19,8 +19,11 @@
19
19
  "open": "^10.0.0",
20
20
  "ora": "^8.0.1",
21
21
  "prompts": "^2.4.2",
22
- "@hypequery/protocol": "0.9.0",
23
- "@hypequery/deployment": "0.5.1"
22
+ "@hypequery/deployment": "0.5.1",
23
+ "@hypequery/protocol": "0.9.0"
24
+ },
25
+ "optionalDependencies": {
26
+ "@hypequery/gateway": "0.2.0"
24
27
  },
25
28
  "peerDependencies": {
26
29
  "@hypequery/clickhouse": "*",
@@ -37,8 +40,8 @@
37
40
  "@vitest/coverage-v8": "^3.2.6",
38
41
  "typescript": "^5.7.3",
39
42
  "vitest": "^3.2.6",
40
- "@hypequery/clickhouse": "2.4.0",
41
- "@hypequery/serve": "0.13.6"
43
+ "@hypequery/clickhouse": "2.5.0",
44
+ "@hypequery/serve": "0.14.0"
42
45
  },
43
46
  "repository": {
44
47
  "type": "git",