Skip to content

Commit 277a5bb

Browse files
committed
feat(cli): add --generate-queries flag to build, compile, and push
Adds a `-g`/`--generate-queries` flag that reads the schema and auto-generates CRUD queries for every node, vector, and edge type: - Nodes: GetAll, Get (by index), Create, Delete, DeleteAll - Vectors: GetAll, Add, Search, DeleteAll - Edges: GetAll, Create, DeleteAll Generated queries are written to `generated_queries.hx` so they're visible, editable, and picked up by normal compilation. Also simplifies the `helix init` queries template to point users at `--generate-queries` instead of shipping hardcoded starter queries. https://claude.ai/code/session_01WvDRFcS5r3kziMrg4ddKhm
1 parent e3adf7c commit 277a5bb

File tree

7 files changed

+289
-67
lines changed

7 files changed

+289
-67
lines changed

helix-cli/src/commands/build.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use crate::project::{ProjectContext, get_helix_repo_cache};
77
use crate::prompts;
88
use crate::utils::{
99
copy_dir_recursive_excluding, diagnostic_source,
10-
helixc_utils::{collect_hx_contents, collect_hx_files},
10+
helixc_utils::{collect_hx_contents, collect_hx_files, generate_default_queries},
1111
print_confirm, print_error, print_warning,
1212
};
1313
use eyre::{Result, eyre};
@@ -42,6 +42,7 @@ const CARGO_MANIFEST_DIR: &str = env!("CARGO_MANIFEST_DIR");
4242
pub async fn run(
4343
instance_name: Option<String>,
4444
bin: Option<String>,
45+
generate_queries: bool,
4546
metrics_sender: &MetricsSender,
4647
) -> Result<MetricsData> {
4748
// Load project context
@@ -82,6 +83,7 @@ pub async fn run(
8283
&project,
8384
&instance_name,
8485
bin.as_deref(),
86+
generate_queries,
8587
metrics_sender,
8688
)
8789
.await;
@@ -100,6 +102,7 @@ pub async fn run_build_steps(
100102
project: &ProjectContext,
101103
instance_name: &str,
102104
bin: Option<&str>,
105+
generate_queries: bool,
103106
metrics_sender: &MetricsSender,
104107
) -> Result<MetricsData> {
105108
let start_time = Instant::now();
@@ -116,6 +119,15 @@ pub async fn run_build_steps(
116119
// Step 2: Prepare workspace (verbose only shows details)
117120
prepare_instance_workspace(project, instance_name).await?;
118121

122+
// Step 2.5: Generate CRUD queries from schema if requested
123+
if generate_queries {
124+
let mut gen_step =
125+
Step::with_messages("Generating queries from schema", "Queries generated");
126+
gen_step.start();
127+
generate_default_queries(&project.root, &project.config.project.queries)?;
128+
gen_step.done();
129+
}
130+
119131
// Step 3: Compile project queries
120132
let mut compile_step = Step::with_messages("Compiling queries", "Queries compiled");
121133
compile_step.start();

helix-cli/src/commands/compile.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,12 @@ use crate::{
66
output::{Operation, Step},
77
project::ProjectContext,
88
utils::helixc_utils::{
9-
analyze_source, collect_hx_files, generate_content, generate_rust_code, parse_content,
9+
analyze_source, collect_hx_files, generate_content, generate_default_queries,
10+
generate_rust_code, parse_content,
1011
},
1112
};
1213

13-
pub async fn run(output_dir: Option<String>, path: Option<String>) -> Result<()> {
14+
pub async fn run(output_dir: Option<String>, path: Option<String>, generate_queries: bool) -> Result<()> {
1415
let op = Operation::new("Compiling", "queries");
1516

1617
// Load project context from the specified path (helix.toml directory) or find it automatically
@@ -22,6 +23,15 @@ pub async fn run(output_dir: Option<String>, path: Option<String>) -> Result<()>
2223
None => ProjectContext::find_and_load(None)?,
2324
};
2425

26+
// Generate CRUD queries from schema if requested
27+
if generate_queries {
28+
let mut gen_step =
29+
Step::with_messages("Generating queries from schema", "Queries generated");
30+
gen_step.start();
31+
generate_default_queries(&project.root, &project.config.project.queries)?;
32+
gen_step.done();
33+
}
34+
2535
// Collect all .hx files for validation from the queries directory
2636
let mut parse_step = Step::with_messages("Parsing queries", "Queries parsed");
2737
parse_step.start();

helix-cli/src/commands/dashboard.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -303,10 +303,10 @@ async fn check_dev_mode_requirement(
303303
config.save_to_file(&config_path)?;
304304

305305
// Reload the project context and push
306-
crate::commands::push::run(Some(instance_name.to_string()), false, &metrics_sender).await?;
306+
crate::commands::push::run(Some(instance_name.to_string()), false, false, &metrics_sender).await?;
307307
} else {
308308
// For cloud instances, use the --dev flag
309-
crate::commands::push::run(Some(instance_name.to_string()), true, &metrics_sender).await?;
309+
crate::commands::push::run(Some(instance_name.to_string()), true, false, &metrics_sender).await?;
310310
}
311311

312312
output::success(&format!(
@@ -369,7 +369,7 @@ async fn check_instance_running(project: &ProjectContext, instance_name: &str) -
369369
instance_name
370370
));
371371
let metrics_sender = MetricsSender::new()?;
372-
crate::commands::push::run(Some(instance_name.to_string()), false, &metrics_sender).await?;
372+
crate::commands::push::run(Some(instance_name.to_string()), false, false, &metrics_sender).await?;
373373
output::success(&format!("Instance '{}' built and started", instance_name));
374374

375375
Ok(())

helix-cli/src/commands/init.rs

Lines changed: 11 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -258,8 +258,8 @@ async fn run_init_inner(
258258
print_instructions(
259259
"Next steps:",
260260
&[
261-
&format!("Customize {queries_path_clean}/schema.hx with your data model"),
262-
&format!("Edit {queries_path_clean}/queries.hx or add your own queries"),
261+
&format!("Define your data model in {queries_path_clean}/schema.hx"),
262+
"Run 'helix build --generate-queries' to generate CRUD queries from your schema",
263263
"Run 'helix push dev' to start your development instance",
264264
],
265265
);
@@ -318,55 +318,15 @@ E::Authored {
318318
fs::write(&schema_path, default_schema)?;
319319
cleanup_tracker.track_file(schema_path);
320320

321-
// Create default queries.hx with starter queries matching the schema
322-
let default_queries = r#"// ─── Node queries ──────────────────────────────────────────
323-
324-
QUERY GetAllUsers() =>
325-
users <- N<User>
326-
RETURN users
327-
328-
QUERY GetUser(name: String) =>
329-
user <- N<User>({name: name})
330-
RETURN user
331-
332-
QUERY CreateUser(name: String, email: String, age: I32) =>
333-
user <- AddN<User>({name: name, email: email, age: age})
334-
RETURN user
335-
336-
QUERY UpdateUser(name: String, email: String) =>
337-
user <- N<User>({name: name})::UPDATE({email: email})
338-
RETURN user
339-
340-
QUERY DeleteUser(name: String) =>
341-
DROP N<User>({name: name})
342-
RETURN "success"
343-
344-
// ─── Vector queries ────────────────────────────────────────
345-
346-
QUERY AddDocument(title: String, content: String, source: String) =>
347-
doc <- AddV<Document>(
348-
Embed(content),
349-
{title: title, content: content, source: source}
350-
)
351-
RETURN doc
352-
353-
QUERY SearchDocuments(query: String, k: I32) =>
354-
results <- SearchV<Document>(Embed(query), k)
355-
RETURN results
356-
357-
// ─── Edge queries ──────────────────────────────────────────
358-
359-
QUERY LinkUserToDocument(name: String, doc_id: ID, role: String) =>
360-
user <- N<User>({name: name})
361-
doc <- V<Document>(doc_id)
362-
edge <- AddE<Authored>({role: role})::From(user)::To(doc)
363-
RETURN edge
364-
365-
QUERY GetUserDocuments(name: String) =>
366-
user <- N<User>({name: name})
367-
docs <- user::Out<Authored>
368-
RETURN docs
369-
321+
// Create default queries.hx with a minimal example
322+
let default_queries = r#"// Write your queries here, or generate CRUD queries from your schema:
323+
// helix build --generate-queries
324+
//
325+
// Example query:
326+
// QUERY GetUser(name: String) =>
327+
// user <- N<User>({name: name})
328+
// RETURN user
329+
//
370330
// For more on HelixQL, see https://docs.helix-db.com
371331
// or visit https://github.com/HelixDB/helix-db
372332
"#;

helix-cli/src/commands/push.rs

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use std::time::Instant;
1616
pub async fn run(
1717
instance_name: Option<String>,
1818
dev: bool,
19+
generate_queries: bool,
1920
metrics_sender: &MetricsSender,
2021
) -> Result<()> {
2122
let start_time = Instant::now();
@@ -58,13 +59,14 @@ pub async fn run(
5859
}
5960

6061
let deploy_result = if instance_config.is_local() {
61-
push_local_instance(&project, &instance_name, metrics_sender).await
62+
push_local_instance(&project, &instance_name, generate_queries, metrics_sender).await
6263
} else {
6364
push_cloud_instance(
6465
&project,
6566
&instance_name,
6667
instance_config.clone(),
6768
dev,
69+
generate_queries,
6870
metrics_sender,
6971
)
7072
.await
@@ -123,6 +125,7 @@ pub async fn run(
123125
async fn push_local_instance(
124126
project: &ProjectContext,
125127
instance_name: &str,
128+
generate_queries: bool,
126129
metrics_sender: &MetricsSender,
127130
) -> Result<MetricsData> {
128131
let op = Operation::new("Deploying", instance_name);
@@ -145,9 +148,15 @@ async fn push_local_instance(
145148
}
146149

147150
// Build the instance first (this ensures it's up to date) and get metrics data
148-
let metrics_data =
149-
crate::commands::build::run_build_steps(&op, project, instance_name, None, metrics_sender)
150-
.await?;
151+
let metrics_data = crate::commands::build::run_build_steps(
152+
&op,
153+
project,
154+
instance_name,
155+
None,
156+
generate_queries,
157+
metrics_sender,
158+
)
159+
.await?;
151160

152161
// If port changed, regenerate docker-compose with new port
153162
if port_changed {
@@ -191,6 +200,7 @@ async fn push_cloud_instance(
191200
instance_name: &str,
192201
instance_config: InstanceInfo<'_>,
193202
dev: bool,
203+
generate_queries: bool,
194204
metrics_sender: &MetricsSender,
195205
) -> Result<MetricsData> {
196206
let op = Operation::new("Deploying", instance_name);
@@ -209,7 +219,13 @@ async fn push_cloud_instance(
209219

210220
let metrics_data = if instance_config.should_build_docker_image() {
211221
// Build happens, get metrics data from build
212-
crate::commands::build::run(Some(instance_name.to_string()), None, metrics_sender).await?
222+
crate::commands::build::run(
223+
Some(instance_name.to_string()),
224+
None,
225+
generate_queries,
226+
metrics_sender,
227+
)
228+
.await?
213229
} else {
214230
// No build, use lightweight parsing
215231
parse_queries_for_metrics(project)?

helix-cli/src/main.rs

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ enum Commands {
8484
/// Path to output compiled queries
8585
#[clap(short, long)]
8686
output: Option<String>,
87+
88+
/// Generate CRUD queries from schema before compiling
89+
#[clap(short = 'g', long = "generate-queries")]
90+
generate_queries: bool,
8791
},
8892

8993
/// Build and compile project for an instance
@@ -94,6 +98,10 @@ enum Commands {
9498
/// Should build HelixDB into a binary at the specified directory location
9599
#[clap(long)]
96100
bin: Option<String>,
101+
102+
/// Generate CRUD queries from schema before building
103+
#[clap(short = 'g', long = "generate-queries")]
104+
generate_queries: bool,
97105
},
98106

99107
/// Deploy/start an instance
@@ -103,6 +111,10 @@ enum Commands {
103111
/// Use development profile for faster builds (Helix Cloud only)
104112
#[clap(long)]
105113
dev: bool,
114+
115+
/// Generate CRUD queries from schema before deploying
116+
#[clap(short = 'g', long = "generate-queries")]
117+
generate_queries: bool,
106118
},
107119

108120
/// Pull .hql files from instance back to local project
@@ -270,13 +282,23 @@ async fn main() -> Result<()> {
270282
commands::create_cluster::run(&instance, region).await
271283
}
272284
Commands::Check { instance } => commands::check::run(instance, &metrics_sender).await,
273-
Commands::Compile { output, path } => commands::compile::run(output, path).await,
274-
Commands::Build { instance, bin } => commands::build::run(instance, bin, &metrics_sender)
285+
Commands::Compile {
286+
output,
287+
path,
288+
generate_queries,
289+
} => commands::compile::run(output, path, generate_queries).await,
290+
Commands::Build {
291+
instance,
292+
bin,
293+
generate_queries,
294+
} => commands::build::run(instance, bin, generate_queries, &metrics_sender)
275295
.await
276296
.map(|_| ()),
277-
Commands::Push { instance, dev } => {
278-
commands::push::run(instance, dev, &metrics_sender).await
279-
}
297+
Commands::Push {
298+
instance,
299+
dev,
300+
generate_queries,
301+
} => commands::push::run(instance, dev, generate_queries, &metrics_sender).await,
280302
Commands::Pull { instance } => commands::pull::run(instance).await,
281303
Commands::Start { instance } => commands::start::run(instance).await,
282304
Commands::Stop { instance } => commands::stop::run(instance).await,

0 commit comments

Comments
 (0)