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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 43 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,51 @@ jobs:
readme-filepath: ./deploy/docker/README.md
short-description: "Lightweight, ultra-fast streaming SQL engine for edge & IoT in Rust"

publish-cargo:
name: Publish Packages to Crates.io
needs: gate-checks
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
ref: ${{ inputs.tag || github.ref }}

- name: Setup Rust toolchain
uses: ./.github/actions/setup-rust

- name: Publish Crates in Topological Order
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
shell: bash
run: |
if [ -z "$CARGO_REGISTRY_TOKEN" ]; then
echo "Notice: CARGO_REGISTRY_TOKEN secret is not set; skipping crates.io publish."
exit 0
fi

CRATES=(
"rekuiper-conf"
"rekuiper-core"
"rekuiper-sql"
"rekuiper-connectors"
"rekuiper-server"
"kuiperd"
"rekuiper-cli"
"rule-engine"
"ruleengine"
)

for crate in "${CRATES[@]}"; do
echo "=== Publishing crate: ${crate} ==="
cargo publish -p "${crate}" --token "${CARGO_REGISTRY_TOKEN}" || true
echo "Sleeping 15 seconds for crates.io index propagation..."
sleep 15
done

publish-release:
name: Publish GitHub Release
needs: [build-release, docker-release]
needs: [build-release, docker-release, publish-cargo]
runs-on: ubuntu-latest
permissions:
contents: write
Expand Down
14 changes: 9 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ members = [
"crates/rekuiper-cli",
"crates/kuiperd",
"crates/rekuiper-mcp",
"crates/rule-engine",
"crates/ruleengine",
]
resolver = "2"

Expand Down Expand Up @@ -65,11 +67,13 @@ boa_engine = "0.20"
yamux = "0.13"

# Internal crate dependencies
rekuiper-core = { path = "crates/rekuiper-core" }
rekuiper-sql = { path = "crates/rekuiper-sql" }
rekuiper-conf = { path = "crates/rekuiper-conf" }
rekuiper-connectors = { path = "crates/rekuiper-connectors" }
rekuiper-server = { path = "crates/rekuiper-server" }
rekuiper-core = { path = "crates/rekuiper-core", version = "1.0.0-rc4" }
rekuiper-sql = { path = "crates/rekuiper-sql", version = "1.0.0-rc4" }
rekuiper-conf = { path = "crates/rekuiper-conf", version = "1.0.0-rc4" }
rekuiper-connectors = { path = "crates/rekuiper-connectors", version = "1.0.0-rc4" }
rekuiper-server = { path = "crates/rekuiper-server", version = "1.0.0-rc4" }
kuiperd = { path = "crates/kuiperd", version = "1.0.0-rc4" }
rule-engine = { path = "crates/rule-engine", version = "1.0.0-rc4" }

[profile.release]
opt-level = 3
Expand Down
81 changes: 78 additions & 3 deletions crates/kuiperd/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,23 @@ use clap::Parser;
use rekuiper_conf::PathConfig;

#[derive(Parser, Debug)]
#[command(name = "rekuiperd")]
#[command(about = "rekuiper Daemon - High-performance edge stream processing engine in Rust (kuiperd drop-in)", long_about = None)]
#[command(name = "rule-engine")]
#[command(
about = "rule-engine Daemon - High-performance edge stream processing engine and MQTT rule engine in Rust (rekuiper / eKuiper drop-in)",
long_about = None
)]
pub struct Args {
#[command(subcommand)]
pub command: Option<Commands>,

/// Path of etc dir
#[arg(long, default_value = "etc")]
pub etc: String,

/// Scaffold default configuration files into the target etc directory and exit
#[arg(long)]
pub init: bool,

/// Path of data dir
#[arg(long, default_value = "data")]
pub data: String,
Expand Down Expand Up @@ -76,10 +86,75 @@ pub struct Args {
pub ui_password: Option<String>,
}

#[derive(clap::Subcommand, Debug)]
pub enum Commands {
/// Scaffold and write default configuration files (kuiper.yaml and connector configurations)
Init {
/// Target directory where configuration files will be written (default: "etc")
#[arg(short, long, default_value = "etc")]
dir: String,

/// Overwrite existing configuration files
#[arg(short, long, default_value_t = false)]
force: bool,
},
}

const DEFAULT_KUIPER_YAML: &str = include_str!("../../../etc/kuiper.yaml");
const DEFAULT_MQTT_SOURCE_YAML: &str = include_str!("../../../etc/mqtt_source.yaml");
const DEFAULT_CLIENT_YAML: &str = include_str!("../../../etc/client.yaml");

/// Scaffolds configuration directory and default YAML files for quickstart.
pub fn scaffold_config(target_dir: &str, force: bool) -> Result<()> {
let base = std::path::Path::new(target_dir);
std::fs::create_dir_all(base)?;

let files = [
("kuiper.yaml", DEFAULT_KUIPER_YAML),
("mqtt_source.yaml", DEFAULT_MQTT_SOURCE_YAML),
("client.yaml", DEFAULT_CLIENT_YAML),
];

println!("Scaffolding configuration in '{}/':", target_dir);
for (filename, content) in files {
let file_path = base.join(filename);
if file_path.exists() && !force {
println!(
" [SKIP] {} already exists (use --force to overwrite)",
file_path.display()
);
} else {
std::fs::write(&file_path, content)?;
println!(" [CREATED] {}", file_path.display());
}
}

if target_dir == "etc" {
let _ = std::fs::create_dir_all("data");
let _ = std::fs::create_dir_all("log");
println!(" [CREATED] ./data/");
println!(" [CREATED] ./log/");
}

println!("\nConfiguration complete! You can now start the daemon:");
println!(" rule-engine");
println!(" # or: kuiperd\n");

Ok(())
}

pub async fn run_daemon() -> Result<()> {
let args = Args::parse();

if let Some(Commands::Init { dir, force }) = args.command {
return scaffold_config(&dir, force);
}
if args.init {
return scaffold_config(&args.etc, false);
}

tracing_subscriber::fmt::init();

let args = Args::parse();
let paths = PathConfig::new(Some(&args.etc), Some(&args.data), Some(&args.log));
let config = paths.load_config()?;

Expand Down
3 changes: 0 additions & 3 deletions crates/rekuiper-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,3 @@ serde_yaml = { workspace = true }
toml = { workspace = true }
regex = { workspace = true }
wasmi = "0.36"

[dev-dependencies]
rekuiper-sql = { workspace = true }
36 changes: 36 additions & 0 deletions crates/rule-engine/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
[package]
name = "rule-engine"
version.workspace = true
edition.workspace = true
license.workspace = true
description = "High-performance edge stream processing engine and MQTT rule engine daemon (rekuiper / eKuiper drop-in)"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
keywords.workspace = true
categories.workspace = true
readme = "README.md"

[lib]
name = "rule_engine"
path = "src/lib.rs"

[[bin]]
name = "rule-engine"
path = "src/main.rs"

[[bin]]
name = "kuiperd"
path = "src/bin/kuiperd.rs"

[[bin]]
name = "rekuiperd"
path = "src/bin/rekuiperd.rs"

[dependencies]
kuiperd = { workspace = true }
rekuiper-sql = { workspace = true }
rekuiper-core = { workspace = true }
tokio = { workspace = true }
anyhow = { workspace = true }
93 changes: 93 additions & 0 deletions crates/rule-engine/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# rule-engine

[![Crates.io](https://img.shields.io/crates/v/rule-engine.svg)](https://crates.io/crates/rule-engine)
[![License](https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-blue.svg)](https://github.com/Indus-Stack/rekuiper)
[![Documentation](https://img.shields.io/badge/docs-rekuiper.indramqtt.com-blue)](https://rekuiper.indramqtt.com)

**rule-engine** (`rekuiper`) is a high-performance, lightweight edge stream processing engine and MQTT rule engine written in Rust. It functions as a drop-in replacement for LF Edge eKuiper (`kuiperd`), delivering sub-millisecond rule evaluation with ultra-low memory overhead on Linux, macOS, Windows, and embedded IoT architectures.

---

## Quickstart

### 1. Install via Cargo

```bash
cargo install rule-engine
```

*Or install pre-compiled binaries instantly:*
```bash
cargo binstall rule-engine
```

### 2. Scaffold Default Configuration

Initialize the configuration files (`kuiper.yaml`, `mqtt_source.yaml`, `client.yaml`):

```bash
rule-engine init
```

### 3. Start the Daemon

```bash
rule-engine
```

The daemon starts:
- **REST API**: `http://127.0.0.1:9081`
- **Embedded Web Console**: `http://127.0.0.1:9081/ui` (when enabled with `--ui`)
- **CLI RPC Port**: `20498`

---

## Use as an Embedded Library in Rust

You can also embed the streaming SQL processor and rule engine directly inside your own Rust application:

```toml
[dependencies]
rule-engine = "1.0.0-rc4"
```

```rust
use rule_engine::sql::Parser;

fn main() -> anyhow::Result<()> {
// Parse streaming SQL queries with windowing, filters, and aggregations
let mut parser = Parser::new("SELECT temperature, humidity FROM sensor WHERE temperature > 50");
let stmt = parser.parse_select()?;
println!("Parsed query: {:?}", stmt);
Ok(())
}
```

Or depend exclusively on the lightweight SQL parsing & execution engine:
```toml
[dependencies]
rekuiper-sql = "1.0.0-rc4"
```

---

## Binary Names

Installing `rule-engine` installs three executable aliases:
1. `rule-engine`
2. `kuiperd` (100% LF Edge eKuiper drop-in compatibility)
3. `rekuiperd`

---

## Documentation & Community

- **Official Documentation**: [https://rekuiper.indramqtt.com](https://rekuiper.indramqtt.com)
- **Source Repository**: [https://github.com/Indus-Stack/rekuiper](https://github.com/Indus-Stack/rekuiper)
- **Docker Images**: `indusstack/rekuiper:latest`

## License

Licensed under either of:
- Apache License, Version 2.0 ([LICENSE-APACHE](https://github.com/Indus-Stack/rekuiper/blob/main/LICENSE-APACHE))
- MIT license ([LICENSE-MIT](https://github.com/Indus-Stack/rekuiper/blob/main/LICENSE-MIT))
6 changes: 6 additions & 0 deletions crates/rule-engine/src/bin/kuiperd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use anyhow::Result;

#[tokio::main]
async fn main() -> Result<()> {
kuiperd::run_daemon().await
}
6 changes: 6 additions & 0 deletions crates/rule-engine/src/bin/rekuiperd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use anyhow::Result;

#[tokio::main]
async fn main() -> Result<()> {
kuiperd::run_daemon().await
}
29 changes: 29 additions & 0 deletions crates/rule-engine/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//! # rule-engine
//!
//! High-performance edge stream processing engine and streaming SQL rule engine in Rust.
//!
//! ## Embedding in Your Own Application
//!
//! You can embed `rule-engine` or its streaming SQL engine directly in your Rust project:
//!
//! ```toml
//! [dependencies]
//! rule-engine = "1.0.0-rc4"
//! ```
//!
//! ### Using the Streaming SQL Engine
//!
//! ```rust
//! use rule_engine::sql::Parser;
//!
//! # fn main() -> anyhow::Result<()> {
//! let sql = "SELECT temperature * 1.8 + 32 AS temp_f FROM sensor WHERE temperature > 20";
//! let mut parser = Parser::new(sql);
//! let stmt = parser.parse_select()?;
//! # Ok(())
//! # }
//! ```

pub use kuiperd::*;
pub use rekuiper_core as core;
pub use rekuiper_sql as sql;
6 changes: 6 additions & 0 deletions crates/rule-engine/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
use anyhow::Result;

#[tokio::main]
async fn main() -> Result<()> {
kuiperd::run_daemon().await
}
22 changes: 22 additions & 0 deletions crates/ruleengine/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
[package]
name = "ruleengine"
version.workspace = true
edition.workspace = true
license.workspace = true
description = "High-performance edge stream processing engine and MQTT rule engine daemon (alias for rule-engine / rekuiper)"
authors.workspace = true
repository.workspace = true
homepage.workspace = true
documentation.workspace = true
keywords.workspace = true
categories.workspace = true
readme = "README.md"

[[bin]]
name = "ruleengine"
path = "src/main.rs"

[dependencies]
kuiperd = { workspace = true }
tokio = { workspace = true }
anyhow = { workspace = true }
Loading
Loading