mirror of
https://github.com/ModernRelay/omnigraph.git
synced 2026-06-18 02:24:27 +02:00
feat(cli): alias subcommand; remove --alias flag (RFC-011 D4) (#244)
Operator aliases move from the --alias flag on query/mutate to a dedicated 'omnigraph alias <name> [args]' subcommand, so an alias can never shadow or be shadowed by a built-in verb. Unknown name errors listing defined aliases. Removes the legacy alias machinery from query/mutate (net -156 lines); legacy omnigraph.yaml aliases lose their CLI entry point.
This commit is contained in:
parent
2ed05d2cb1
commit
b395757e21
9 changed files with 128 additions and 379 deletions
|
|
@ -99,12 +99,10 @@ pub(crate) enum Command {
|
|||
legacy_uri: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long, conflicts_with_all = ["query", "query_string"])]
|
||||
alias: Option<String>,
|
||||
#[arg(long, conflicts_with_all = ["alias", "query_string"])]
|
||||
#[arg(long, conflicts_with = "query_string")]
|
||||
query: Option<PathBuf>,
|
||||
/// Inline GQ source — alternative to `--query <path>` and `--alias <name>`.
|
||||
#[arg(short = 'e', long = "query-string", value_name = "GQ", conflicts_with_all = ["query", "alias"])]
|
||||
/// Inline GQ source — alternative to `--query <path>`.
|
||||
#[arg(short = 'e', long = "query-string", value_name = "GQ", conflicts_with = "query")]
|
||||
query_string: Option<String>,
|
||||
#[arg(long)]
|
||||
name: Option<String>,
|
||||
|
|
@ -118,8 +116,6 @@ pub(crate) enum Command {
|
|||
format: Option<ReadOutputFormat>,
|
||||
#[arg(long, conflicts_with = "format")]
|
||||
json: bool,
|
||||
#[arg()]
|
||||
alias_args: Vec<String>,
|
||||
},
|
||||
/// Execute a graph mutation query against a branch.
|
||||
///
|
||||
|
|
@ -135,12 +131,10 @@ pub(crate) enum Command {
|
|||
legacy_uri: Option<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[arg(long, conflicts_with_all = ["query", "query_string"])]
|
||||
alias: Option<String>,
|
||||
#[arg(long, conflicts_with_all = ["alias", "query_string"])]
|
||||
#[arg(long, conflicts_with = "query_string")]
|
||||
query: Option<PathBuf>,
|
||||
/// Inline GQ source — alternative to `--query <path>` and `--alias <name>`.
|
||||
#[arg(short = 'e', long = "query-string", value_name = "GQ", conflicts_with_all = ["query", "alias"])]
|
||||
/// Inline GQ source — alternative to `--query <path>`.
|
||||
#[arg(short = 'e', long = "query-string", value_name = "GQ", conflicts_with = "query")]
|
||||
query_string: Option<String>,
|
||||
#[arg(long)]
|
||||
name: Option<String>,
|
||||
|
|
@ -150,8 +144,28 @@ pub(crate) enum Command {
|
|||
branch: Option<String>,
|
||||
#[arg(long)]
|
||||
json: bool,
|
||||
#[arg()]
|
||||
alias_args: Vec<String>,
|
||||
},
|
||||
/// Invoke an operator alias (RFC-011 Decision 4).
|
||||
///
|
||||
/// An alias is a personal binding under `aliases:` in
|
||||
/// ~/.omnigraph/config.yaml — name → (server, graph, stored-query name,
|
||||
/// default params). `omnigraph alias <name> [args]` invokes the bound
|
||||
/// stored query on its server. Living in its own namespace, an alias can
|
||||
/// never shadow or be shadowed by a built-in verb. Replaces the removed
|
||||
/// `--alias` flag on `query`/`mutate`.
|
||||
Alias {
|
||||
/// Alias name (a key under `aliases:` in ~/.omnigraph/config.yaml).
|
||||
name: String,
|
||||
/// Positional args bound to the alias's declared `args` params, in order.
|
||||
args: Vec<String>,
|
||||
#[arg(long)]
|
||||
config: Option<PathBuf>,
|
||||
#[command(flatten)]
|
||||
params: ParamsArgs,
|
||||
#[arg(long, conflicts_with = "json")]
|
||||
format: Option<ReadOutputFormat>,
|
||||
#[arg(long, conflicts_with = "format")]
|
||||
json: bool,
|
||||
},
|
||||
/// Load data into a graph (local or remote)
|
||||
Load {
|
||||
|
|
|
|||
|
|
@ -729,44 +729,6 @@ pub(crate) fn parse_alias_value(value: &str) -> Value {
|
|||
serde_json::from_str(value).unwrap_or_else(|_| Value::String(value.to_string()))
|
||||
}
|
||||
|
||||
pub(crate) fn merged_params_json(
|
||||
alias_name: Option<&str>,
|
||||
alias_arg_names: &[String],
|
||||
alias_arg_values: &[String],
|
||||
explicit: Option<Value>,
|
||||
) -> Result<Option<Value>> {
|
||||
if alias_arg_values.len() > alias_arg_names.len() {
|
||||
let alias = alias_name.unwrap_or("<alias>");
|
||||
bail!(
|
||||
"alias '{}' expects at most {} args but got {}",
|
||||
alias,
|
||||
alias_arg_names.len(),
|
||||
alias_arg_values.len()
|
||||
);
|
||||
}
|
||||
|
||||
let mut merged = serde_json::Map::new();
|
||||
for (arg_name, arg_value) in alias_arg_names.iter().zip(alias_arg_values.iter()) {
|
||||
merged.insert(arg_name.clone(), parse_alias_value(arg_value));
|
||||
}
|
||||
|
||||
match explicit {
|
||||
Some(Value::Object(object)) => {
|
||||
for (key, value) in object {
|
||||
merged.insert(key, value);
|
||||
}
|
||||
}
|
||||
Some(_) => bail!("params JSON must be an object"),
|
||||
None => {}
|
||||
}
|
||||
|
||||
if merged.is_empty() {
|
||||
Ok(None)
|
||||
} else {
|
||||
Ok(Some(Value::Object(merged)))
|
||||
}
|
||||
}
|
||||
|
||||
/// The format cascade (RFC-007 §D3): `--json` > `--format` > alias format >
|
||||
/// legacy `cli.output_format` (RFC-008 window) > operator `defaults.output`
|
||||
/// > table.
|
||||
|
|
@ -790,43 +752,6 @@ pub(crate) fn resolve_read_format(
|
|||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_alias<'a>(
|
||||
config: &'a OmnigraphConfig,
|
||||
alias_name: Option<&'a str>,
|
||||
expected: AliasCommand,
|
||||
) -> Result<Option<(&'a str, &'a omnigraph_server::AliasConfig)>> {
|
||||
let Some(alias_name) = alias_name else {
|
||||
return Ok(None);
|
||||
};
|
||||
let alias = config.alias(alias_name)?;
|
||||
if alias.command != expected {
|
||||
bail!(
|
||||
"alias '{}' is a {:?} alias, not a {:?} alias",
|
||||
alias_name,
|
||||
alias.command,
|
||||
expected
|
||||
);
|
||||
}
|
||||
Ok(Some((alias_name, alias)))
|
||||
}
|
||||
|
||||
pub(crate) fn normalize_legacy_alias_uri(
|
||||
uri: Option<String>,
|
||||
target_available: bool,
|
||||
alias_name: Option<&str>,
|
||||
mut alias_args: Vec<String>,
|
||||
) -> (Option<String>, Vec<String>) {
|
||||
let Some(candidate) = uri else {
|
||||
return (None, alias_args);
|
||||
};
|
||||
|
||||
if alias_name.is_some() && target_available {
|
||||
alias_args.insert(0, candidate);
|
||||
return (None, alias_args);
|
||||
}
|
||||
|
||||
(Some(candidate), alias_args)
|
||||
}
|
||||
|
||||
|
||||
pub(crate) fn read_target_from_cli(branch: Option<String>, snapshot: Option<String>) -> ReadTarget {
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ use omnigraph_api_types::{
|
|||
};
|
||||
use omnigraph_server::queries::{QueryRegistry, check, format_check_breakages};
|
||||
use omnigraph_server::{
|
||||
AliasCommand, OmnigraphConfig, PolicyAction, PolicyDecision, PolicyEngine, PolicyRequest,
|
||||
OmnigraphConfig, PolicyAction, PolicyDecision, PolicyEngine, PolicyRequest,
|
||||
PolicyTestConfig, ReadOutputFormat, graph_resource_id_for_selection, load_config,
|
||||
};
|
||||
use reqwest::Method;
|
||||
|
|
@ -569,7 +569,6 @@ async fn main() -> Result<()> {
|
|||
uri,
|
||||
legacy_uri,
|
||||
config,
|
||||
alias,
|
||||
query,
|
||||
query_string,
|
||||
name,
|
||||
|
|
@ -578,182 +577,61 @@ async fn main() -> Result<()> {
|
|||
snapshot,
|
||||
format,
|
||||
json,
|
||||
alias_args,
|
||||
} => {
|
||||
if alias.is_none() && query.is_none() && query_string.is_none() {
|
||||
bail!("exactly one of --query, --query-string, or --alias must be provided");
|
||||
if query.is_none() && query_string.is_none() {
|
||||
bail!("provide a query: --query <file> or -e '<inline gq>'");
|
||||
}
|
||||
|
||||
let config = load_cli_config(config.as_ref())?;
|
||||
// Operator aliases (RFC-007 PR 3): pure bindings to stored
|
||||
// queries. A legacy file-alias with the same name wins during
|
||||
// the RFC-008 window (with a warning); an alias name found
|
||||
// only in the operator layer takes the invoke path here.
|
||||
if let Some(alias_name) = alias.as_deref() {
|
||||
let operator_config = crate::operator::load_operator_config()?;
|
||||
if let Some(operator_alias) = operator_config.aliases.get(alias_name) {
|
||||
if config.alias(alias_name).is_ok() {
|
||||
eprintln!(
|
||||
"warning: alias '{alias_name}' is defined in both omnigraph.yaml (legacy, wins during the deprecation window) and the operator config; the legacy definition applies"
|
||||
);
|
||||
} else {
|
||||
// The hidden legacy-uri positional swallows the first
|
||||
// bare arg; an operator alias always knows its target,
|
||||
// so reclaim it as the first positional param.
|
||||
let (_, alias_args) = normalize_legacy_alias_uri(
|
||||
legacy_uri.clone(),
|
||||
true,
|
||||
Some(alias_name),
|
||||
alias_args.clone(),
|
||||
);
|
||||
let output = execute_operator_alias(
|
||||
&http_client,
|
||||
&config,
|
||||
alias_name,
|
||||
operator_alias,
|
||||
&alias_args,
|
||||
load_params_json(¶ms)?,
|
||||
)
|
||||
.await?;
|
||||
let format =
|
||||
resolve_read_format(&config, format, json, operator_alias.format);
|
||||
print_read_output(&output, format, &config)?;
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
}
|
||||
let alias = resolve_alias(&config, alias.as_deref(), AliasCommand::Read)?;
|
||||
let alias_name = alias.as_ref().map(|(name, _)| *name);
|
||||
let alias_config = alias.as_ref().map(|(_, alias)| *alias);
|
||||
let alias_graph = alias_config.and_then(|alias| alias.graph.as_deref());
|
||||
let target_available = alias_graph.is_some() || config.cli_graph_name().is_some();
|
||||
let (legacy_uri, alias_args) =
|
||||
normalize_legacy_alias_uri(legacy_uri, target_available, alias_name, alias_args);
|
||||
// `--target` is gone; resolve an alias's legacy `graph` name to its
|
||||
// URI (a positional URI still wins).
|
||||
let uri = match uri.or(legacy_uri) {
|
||||
Some(uri) => Some(uri),
|
||||
None => match alias_graph {
|
||||
Some(name) => Some(config.resolve_target_uri(None, Some(name), None)?),
|
||||
None => None,
|
||||
},
|
||||
};
|
||||
let client = client::GraphClient::resolve(
|
||||
&config,
|
||||
cli.server.as_deref(),
|
||||
cli.graph.as_deref(),
|
||||
uri,
|
||||
uri.or(legacy_uri),
|
||||
cli.profile.as_deref(),
|
||||
cli.store.as_deref(),
|
||||
)?;
|
||||
let query_source = resolve_query_source(
|
||||
&config,
|
||||
query.as_ref(),
|
||||
query_string.as_deref(),
|
||||
alias_config.map(|a| a.query.as_str()),
|
||||
)?;
|
||||
let params_json = merged_params_json(
|
||||
alias_name,
|
||||
alias_config
|
||||
.map(|alias| alias.args.as_slice())
|
||||
.unwrap_or(&[]),
|
||||
&alias_args,
|
||||
load_params_json(¶ms)?,
|
||||
)?;
|
||||
let target = resolve_read_target(
|
||||
&config,
|
||||
branch,
|
||||
snapshot,
|
||||
alias_config.and_then(|alias| alias.branch.clone()),
|
||||
)?;
|
||||
let query_name = name.or_else(|| alias_config.and_then(|alias| alias.name.clone()));
|
||||
let query_source =
|
||||
resolve_query_source(&config, query.as_ref(), query_string.as_deref(), None)?;
|
||||
let params_json = load_params_json(¶ms)?;
|
||||
let target = resolve_read_target(&config, branch, snapshot, None)?;
|
||||
let output = client
|
||||
.query(
|
||||
target,
|
||||
&query_source,
|
||||
query_name.as_deref(),
|
||||
params_json.as_ref(),
|
||||
)
|
||||
.query(target, &query_source, name.as_deref(), params_json.as_ref())
|
||||
.await?;
|
||||
let format = resolve_read_format(
|
||||
&config,
|
||||
format,
|
||||
json,
|
||||
alias_config.and_then(|alias| alias.format),
|
||||
);
|
||||
let format = resolve_read_format(&config, format, json, None);
|
||||
print_read_output(&output, format, &config)?;
|
||||
}
|
||||
Command::Mutate {
|
||||
uri,
|
||||
legacy_uri,
|
||||
config,
|
||||
alias,
|
||||
query,
|
||||
query_string,
|
||||
name,
|
||||
params,
|
||||
branch,
|
||||
json,
|
||||
alias_args,
|
||||
} => {
|
||||
if alias.is_none() && query.is_none() && query_string.is_none() {
|
||||
bail!("exactly one of --query, --query-string, or --alias must be provided");
|
||||
if query.is_none() && query_string.is_none() {
|
||||
bail!("provide a mutation query: --query <file> or -e '<inline gq>'");
|
||||
}
|
||||
|
||||
let config = load_cli_config(config.as_ref())?;
|
||||
let alias = resolve_alias(&config, alias.as_deref(), AliasCommand::Change)?;
|
||||
let alias_name = alias.as_ref().map(|(name, _)| *name);
|
||||
let alias_config = alias.as_ref().map(|(_, alias)| *alias);
|
||||
let alias_graph = alias_config.and_then(|alias| alias.graph.as_deref());
|
||||
let target_available = alias_graph.is_some() || config.cli_graph_name().is_some();
|
||||
let (legacy_uri, alias_args) =
|
||||
normalize_legacy_alias_uri(legacy_uri, target_available, alias_name, alias_args);
|
||||
// `--target` is gone; resolve an alias's legacy `graph` name to its
|
||||
// URI (a positional URI still wins).
|
||||
let uri = match uri.or(legacy_uri) {
|
||||
Some(uri) => Some(uri),
|
||||
None => match alias_graph {
|
||||
Some(name) => Some(config.resolve_target_uri(None, Some(name), None)?),
|
||||
None => None,
|
||||
},
|
||||
};
|
||||
let client = client::GraphClient::resolve_with_policy(
|
||||
&config,
|
||||
cli.server.as_deref(),
|
||||
cli.graph.as_deref(),
|
||||
uri,
|
||||
uri.or(legacy_uri),
|
||||
cli.as_actor.as_deref(),
|
||||
cli.profile.as_deref(),
|
||||
cli.store.as_deref(),
|
||||
)?;
|
||||
let query_source = resolve_query_source(
|
||||
&config,
|
||||
query.as_ref(),
|
||||
query_string.as_deref(),
|
||||
alias_config.map(|a| a.query.as_str()),
|
||||
)?;
|
||||
let params_json = merged_params_json(
|
||||
alias_name,
|
||||
alias_config
|
||||
.map(|alias| alias.args.as_slice())
|
||||
.unwrap_or(&[]),
|
||||
&alias_args,
|
||||
load_params_json(¶ms)?,
|
||||
)?;
|
||||
let branch = resolve_branch(
|
||||
&config,
|
||||
branch,
|
||||
alias_config.and_then(|alias| alias.branch.clone()),
|
||||
"main",
|
||||
);
|
||||
let query_name = name.or_else(|| alias_config.and_then(|alias| alias.name.clone()));
|
||||
let query_source =
|
||||
resolve_query_source(&config, query.as_ref(), query_string.as_deref(), None)?;
|
||||
let params_json = load_params_json(¶ms)?;
|
||||
let branch = resolve_branch(&config, branch, None, "main");
|
||||
let output = client
|
||||
.mutate(
|
||||
&branch,
|
||||
&query_source,
|
||||
query_name.as_deref(),
|
||||
params_json.as_ref(),
|
||||
)
|
||||
.mutate(&branch, &query_source, name.as_deref(), params_json.as_ref())
|
||||
.await?;
|
||||
if json {
|
||||
print_json(&output)?;
|
||||
|
|
@ -761,6 +639,37 @@ async fn main() -> Result<()> {
|
|||
print_change_human(&output);
|
||||
}
|
||||
}
|
||||
Command::Alias {
|
||||
name,
|
||||
args,
|
||||
config,
|
||||
params,
|
||||
format,
|
||||
json,
|
||||
} => {
|
||||
let config = load_cli_config(config.as_ref())?;
|
||||
let operator_config = crate::operator::load_operator_config()?;
|
||||
let Some(operator_alias) = operator_config.aliases.get(&name) else {
|
||||
let defined: Vec<&str> =
|
||||
operator_config.aliases.keys().map(String::as_str).collect();
|
||||
bail!(
|
||||
"unknown alias '{name}'; defined aliases: [{}] \
|
||||
(add it under `aliases:` in ~/.omnigraph/config.yaml)",
|
||||
defined.join(", ")
|
||||
);
|
||||
};
|
||||
let output = execute_operator_alias(
|
||||
&http_client,
|
||||
&config,
|
||||
&name,
|
||||
operator_alias,
|
||||
&args,
|
||||
load_params_json(¶ms)?,
|
||||
)
|
||||
.await?;
|
||||
let format = resolve_read_format(&config, format, json, operator_alias.format);
|
||||
print_read_output(&output, format, &config)?;
|
||||
}
|
||||
Command::Policy { command } => match command {
|
||||
PolicyCommand::Validate { config } => {
|
||||
let config = load_cli_config(config.as_ref())?;
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ pub(crate) fn command_plane(cmd: &Command) -> Plane {
|
|||
match cmd {
|
||||
Command::Query { .. }
|
||||
| Command::Mutate { .. }
|
||||
| Command::Alias { .. }
|
||||
| Command::Load { .. }
|
||||
| Command::Ingest { .. }
|
||||
| Command::Branch { .. }
|
||||
|
|
@ -168,6 +169,7 @@ pub(crate) fn command_label(cmd: &Command) -> &'static str {
|
|||
Command::Commit { .. } => "commit",
|
||||
Command::Query { .. } => "query",
|
||||
Command::Mutate { .. } => "mutate",
|
||||
Command::Alias { .. } => "alias",
|
||||
Command::Policy { .. } => "policy",
|
||||
Command::Optimize { .. } => "optimize",
|
||||
Command::Repair { .. } => "repair",
|
||||
|
|
|
|||
|
|
@ -505,10 +505,9 @@ query list_people() {
|
|||
|
||||
#[test]
|
||||
fn deprecated_read_and_change_subcommands_emit_warnings() {
|
||||
// Both subcommands require `--query`/`--query-string`/`--alias`, so
|
||||
// invoking them with no args will exit non-zero. That's fine --
|
||||
// we only care that the deprecation warning is printed before the
|
||||
// argument-required error.
|
||||
// Both subcommands require `--query`/`--query-string`, so invoking them
|
||||
// with no args will exit non-zero. That's fine -- we only care that the
|
||||
// deprecation warning is printed before the argument-required error.
|
||||
let output = cli().arg("read").output().unwrap();
|
||||
let stderr = String::from_utf8(output.stderr).unwrap();
|
||||
assert!(
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@
|
|||
//! Moved verbatim from tests/cli.rs in the modularization.
|
||||
|
||||
|
||||
use serde_json::Value;
|
||||
use tempfile::tempdir;
|
||||
|
||||
mod support;
|
||||
|
|
@ -57,141 +56,42 @@ query list_people() {
|
|||
assert_eq!(stdout_string(&lint_output), stdout_string(&check_output));
|
||||
}
|
||||
|
||||
// Legacy `omnigraph.yaml` `aliases:` invoked via the `--alias` flag were
|
||||
// removed in RFC-011 D4 — operator aliases now live under `omnigraph alias
|
||||
// <name>` (the happy path is covered by system_local's operator-alias e2e).
|
||||
// The legacy file-alias path has no CLI entry point.
|
||||
|
||||
#[test]
|
||||
fn read_alias_from_yaml_config_runs_with_kv_output() {
|
||||
let temp = tempdir().unwrap();
|
||||
let graph = graph_path(temp.path());
|
||||
let config = temp.path().join("omnigraph.yaml");
|
||||
let query = temp.path().join("aliases.gq");
|
||||
init_graph(&graph);
|
||||
load_fixture(&graph);
|
||||
write_query_file(
|
||||
&query,
|
||||
&std::fs::read_to_string(fixture("test.gq")).unwrap(),
|
||||
fn alias_flag_is_removed_from_query() {
|
||||
// RFC-011 D4: `--alias` no longer exists on query/mutate; use `alias <name>`.
|
||||
let output = output_failure(cli().arg("query").arg("--alias").arg("who"));
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
assert!(
|
||||
stderr.contains("unexpected argument") && stderr.contains("--alias"),
|
||||
"expected clap to reject --alias on query; got: {stderr}"
|
||||
);
|
||||
write_config(
|
||||
&config,
|
||||
&format!(
|
||||
"{}aliases:\n owner:\n command: read\n query: aliases.gq\n name: get_person\n args: [name]\n format: kv\n",
|
||||
local_yaml_config(&graph)
|
||||
),
|
||||
);
|
||||
|
||||
let output = output_success(
|
||||
cli()
|
||||
.arg("read")
|
||||
.arg("--config")
|
||||
.arg(&config)
|
||||
.arg("--alias")
|
||||
.arg("owner")
|
||||
.arg("Alice"),
|
||||
);
|
||||
let stdout = stdout_string(&output);
|
||||
|
||||
assert!(stdout.contains("row 1"));
|
||||
assert!(stdout.contains("p.name: Alice"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_alias_uses_alias_target_without_cli_default_and_accepts_url_like_arg() {
|
||||
let temp = tempdir().unwrap();
|
||||
let graph = graph_path(temp.path());
|
||||
let config = temp.path().join("omnigraph.yaml");
|
||||
let query = temp.path().join("aliases.gq");
|
||||
let data = temp.path().join("url-like.jsonl");
|
||||
init_graph(&graph);
|
||||
write_jsonl(
|
||||
&data,
|
||||
r#"{"type":"Person","data":{"name":"https://example.com","age":30}}"#,
|
||||
);
|
||||
output_success(
|
||||
fn alias_unknown_name_errors_listing_defined() {
|
||||
// Hermetic: an unknown alias fails before any network, listing defined ones.
|
||||
let home = tempdir().unwrap();
|
||||
std::fs::write(
|
||||
home.path().join("config.yaml"),
|
||||
"servers:\n dev:\n url: https://x\naliases:\n who:\n server: dev\n query: find_person\n",
|
||||
)
|
||||
.unwrap();
|
||||
let output = output_failure(
|
||||
cli()
|
||||
.arg("load")
|
||||
.arg("--mode")
|
||||
.arg("overwrite")
|
||||
.arg("--data")
|
||||
.arg(&data)
|
||||
.arg(&graph),
|
||||
.env("OMNIGRAPH_HOME", home.path())
|
||||
.arg("alias")
|
||||
.arg("nope"),
|
||||
);
|
||||
write_query_file(
|
||||
&query,
|
||||
&std::fs::read_to_string(fixture("test.gq")).unwrap(),
|
||||
let stderr = String::from_utf8_lossy(&output.stderr);
|
||||
assert!(
|
||||
stderr.contains("unknown alias 'nope'") && stderr.contains("who"),
|
||||
"expected an unknown-alias error listing defined aliases; got: {stderr}"
|
||||
);
|
||||
write_config(
|
||||
&config,
|
||||
&format!(
|
||||
"graphs:\n local:\n uri: '{}'\nquery:\n roots:\n - .\npolicy: {{}}\naliases:\n owner:\n command: read\n query: aliases.gq\n name: get_person\n args: [name]\n graph: local\n format: kv\n",
|
||||
graph.to_string_lossy()
|
||||
),
|
||||
);
|
||||
|
||||
let output = output_success(
|
||||
cli()
|
||||
.arg("read")
|
||||
.arg("--config")
|
||||
.arg(&config)
|
||||
.arg("--alias")
|
||||
.arg("owner")
|
||||
.arg("https://example.com"),
|
||||
);
|
||||
let stdout = stdout_string(&output);
|
||||
|
||||
assert!(stdout.contains("row 1"));
|
||||
assert!(stdout.contains("p.name: https://example.com"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn change_alias_from_yaml_config_persists_changes() {
|
||||
let temp = tempdir().unwrap();
|
||||
let graph = graph_path(temp.path());
|
||||
let config = temp.path().join("omnigraph.yaml");
|
||||
let query = temp.path().join("mutations.gq");
|
||||
init_graph(&graph);
|
||||
load_fixture(&graph);
|
||||
write_query_file(
|
||||
&query,
|
||||
r#"
|
||||
query insert_person($name: String, $age: I32) {
|
||||
insert Person { name: $name, age: $age }
|
||||
}
|
||||
"#,
|
||||
);
|
||||
write_config(
|
||||
&config,
|
||||
&format!(
|
||||
"{}aliases:\n add_person:\n command: change\n query: mutations.gq\n name: insert_person\n args: [name, age]\n",
|
||||
local_yaml_config(&graph)
|
||||
),
|
||||
);
|
||||
|
||||
let output = output_success(
|
||||
cli()
|
||||
.arg("change")
|
||||
.arg("--config")
|
||||
.arg(&config)
|
||||
.arg("--alias")
|
||||
.arg("add_person")
|
||||
.arg("Eve")
|
||||
.arg("29")
|
||||
.arg("--json"),
|
||||
);
|
||||
let payload: Value = serde_json::from_slice(&output.stdout).unwrap();
|
||||
assert_eq!(payload["affected_nodes"], 1);
|
||||
|
||||
let verify = output_success(
|
||||
cli()
|
||||
.arg("read")
|
||||
.arg(&graph)
|
||||
.arg("--query")
|
||||
.arg(fixture("test.gq"))
|
||||
.arg("--name")
|
||||
.arg("get_person")
|
||||
.arg("--params")
|
||||
.arg(r#"{"name":"Eve"}"#)
|
||||
.arg("--json"),
|
||||
);
|
||||
let verify_payload: Value = serde_json::from_slice(&verify.stdout).unwrap();
|
||||
assert_eq!(verify_payload["row_count"], 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
|||
|
|
@ -2480,12 +2480,11 @@ fn local_cli_operator_alias_and_server_flag_invoke_stored_query() {
|
|||
.unwrap();
|
||||
}
|
||||
|
||||
// The operator alias: name + positional arg, nothing else — server,
|
||||
// The operator alias (RFC-011 D4): `alias <name> [args]` — server,
|
||||
// graph, stored query, and token all resolve from the operator layer.
|
||||
let output = cli()
|
||||
.env("OMNIGRAPH_HOME", operator_home.path())
|
||||
.arg("query")
|
||||
.arg("--alias")
|
||||
.arg("alias")
|
||||
.arg("who")
|
||||
.arg("Alice")
|
||||
.arg("--json")
|
||||
|
|
|
|||
|
|
@ -30,8 +30,9 @@ omnigraph mutate --uri graph.omni \
|
|||
--params '{"name":"Inline","age":42}'
|
||||
```
|
||||
|
||||
`-e` is mutually exclusive with `--query <path>` and `--alias <name>`; exactly
|
||||
one of the three must be provided. The inline source travels through the same
|
||||
`-e` is mutually exclusive with `--query <path>`; exactly one of the two must be
|
||||
provided. (Operator aliases moved to their own `omnigraph alias <name>`
|
||||
subcommand — RFC-011 D4.) The inline source travels through the same
|
||||
parser, lint, params binding, and commit machinery as a file-based query —
|
||||
only the source loader changes.
|
||||
|
||||
|
|
|
|||
|
|
@ -11,8 +11,9 @@ Top-level command families and subcommands. Graph-targeting commands accept a po
|
|||
| `init` | `--schema <pg>` → initialize a graph (no longer scaffolds `omnigraph.yaml`; start cluster configs from the [cluster.md](../clusters/index.md) quick-start or `config migrate`) |
|
||||
| `load` | bulk load a branch, local or remote (`--mode overwrite\|append\|merge` is **required** — overwrite is destructive, so there is no default). Without `--from` the target branch must exist; `--from <base>` forks a missing `--branch` from `<base>` first |
|
||||
| `ingest` | deprecated alias of `load --from <base>` (defaults: `--from main --mode merge`); prints a one-line warning to stderr |
|
||||
| `query` (alias: `read`) | run named read query; source via `--query <path>`, `-e`/`--query-string <GQ>`, or `--alias <name>` (exactly one). `read` is the deprecated previous name and prints a one-line warning to stderr |
|
||||
| `mutate` (alias: `change`) | run mutation query; same `--query` / `-e` / `--alias` mutual-exclusion as `query`. `change` is the deprecated previous name and prints a one-line warning to stderr |
|
||||
| `query` (alias: `read`) | run a read query; source via `--query <path>` or `-e`/`--query-string <GQ>`. `read` is the deprecated previous name and prints a one-line warning to stderr |
|
||||
| `mutate` (alias: `change`) | run a mutation query; same `--query` / `-e` source as `query`. `change` is the deprecated previous name and prints a one-line warning to stderr |
|
||||
| `alias <name> [args]` | invoke an operator alias — a personal binding (under `aliases:` in `~/.omnigraph/config.yaml`) to a stored query on a named server (RFC-011 D4; replaces the removed `--alias` flag) |
|
||||
| `snapshot` | print current snapshot (per-table version + row count) |
|
||||
| `export` | dump to JSONL on stdout (`--type T`, `--table K` filters) |
|
||||
| `branch create \| list \| delete \| merge` | branching ops |
|
||||
|
|
@ -146,10 +147,12 @@ aliases:
|
|||
format: table
|
||||
```
|
||||
|
||||
`omnigraph query --alias triage 2026-06-01` invokes
|
||||
`omnigraph alias triage 2026-06-01` invokes
|
||||
`POST <server>/graphs/spike/queries/weekly_triage` with the keyed
|
||||
credential. A legacy `omnigraph.yaml` alias with the same name wins during
|
||||
the deprecation window (with a warning).
|
||||
credential. Aliases live in their own `alias` namespace (RFC-011 Decision 4),
|
||||
so an alias can never shadow — or be shadowed by — a built-in verb. (The old
|
||||
`--alias <name>` flag on `query`/`mutate` was removed; legacy `omnigraph.yaml`
|
||||
`aliases:` no longer have a CLI entry point.)
|
||||
|
||||
A remote command whose URL prefix-matches an operator server's `url` (the
|
||||
`gh` host model — no flags needed) resolves its token through:
|
||||
|
|
@ -199,14 +202,11 @@ query:
|
|||
roots: [<dir>, …] # search path for .gq files
|
||||
auth:
|
||||
env_file: .env.omni
|
||||
aliases:
|
||||
<alias>:
|
||||
# accepted values: `read` / `query` (read alias), `change` / `mutate`
|
||||
# (write alias). `query` and `mutate` are recommended; `read` and
|
||||
# `change` remain accepted forever for back-compat.
|
||||
command: read|change|query|mutate
|
||||
query: <path-to-.gq>
|
||||
name: <query-name>
|
||||
aliases: # legacy file-aliases — parsed but no longer
|
||||
<alias>: # reachable from the CLI (RFC-011 D4 removed
|
||||
command: read|change|query|mutate # the `--alias` flag). Use operator
|
||||
query: <path-to-.gq> # aliases (`~/.omnigraph/config.yaml`
|
||||
name: <query-name> # `aliases:`) via `omnigraph alias <name>`.
|
||||
args: [<positional-name>, …]
|
||||
graph: <name>
|
||||
branch: <name>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue